Introduction / Background
In my last post some months ago I showed a use case for weak events*. Since then, I've ran into some other interesting event problems, and that gave me time to make some improvements over Daniel's code.
* If you're not familiar with the concept of weak events, I believe the best introduction you can find is Daniel Grunwald's article.
Weak Events Enhancements Part One: Faster Forwarder Delegates / DynamicMethod vs. Lambda Expression Compilation
The first improvement that I made is, in fact, the last that I've implemented. In Daniel's final solution (FastSmartWeakEvent), a forwarder method is compiled using System.Reflection.Emit.DynamicMethod
. Although this is a very light way to generate code dynamically, it's very low level. Usually, the generating code is quite unreadable and very hard to debug, to modify or to extend. Daniel's first version, for example, had a type safety issue. I imagine that it was hard to find, and the solution is not obvious to understand (at least it wasn't obvious for me).
For some time I was wondering if there was any higher level alternative to a raw DynamicMethod. The usual way to hide low level code is by encapsulating it inside a higher level framework. I don't remember how the idea came to me at the time, but I realized that .NET Framework 3.5 already contains what I was looking for: Expression Trees.
Expressions trees can be composed dynamically, and compiled to a delegate whenever they are ready. I tried it out, and developed a new WeakEventForwarderProvider
class. The implementation details have several interesting catches. In fact, I think there are enough of them that I'll cover them in another post. Until then, feel free to check out the code. For now, I'll share the results that I've found.
Maintainability: Readability is a key feature of a maintainable code, but opinions might always diverge about it. It depends on coding style, and my style is somewhat unusual. Even though I admit the new code is not easy to read, I feel that it has improved a bit from the original; and there is still room for improvement. Debugging, on the other hand, has certainly been improved. The main reason for this is that the debugger is able to show any intermediate expression as nice programmer-readable strings. They can also be converted to string. So, it's much easier to know what is being generated.
Correctness: Using DynamicMethod, it's possible to generate any sequence of IL commands. One obvious advantage of this is a very high flexibility. The trade-off is that it's too easy to generate broken code. Expression trees work as middle ground solution. Its major restriction is that statements are not allowed inside expressions. Fortunately, that can be worked around, specially when generating small pieces of code (I promise to explain how in a future post). A great advantage expressions do provide is a very good validation at dynamic-code-generation-time. The type safety bug that leaked from Daniel's first version of WeakEvents was found and fixed very easily using expressions.
Compiling Performance: I knew that expression compilation uses DynamicMethod internally, so I took for granted that it would be slower to compile. Surprisingly enough, the results that I've measured were close to a tie. Sometimes it appeared that lambda compilation was faster. This is still puzzling to me. While I don't find a good explanation, I'll let the topic open for discussion. My best guess is that the overhead that expression trees add is so much smaller than the overhead of emitting the code that, proportionately, the first can be disregarded.
Executing Performance: Code execution performance is a tricky subject. You can always try to tweak assembly code to make it faster. You can do it at IL level with DynamicMethod too. But that generally is a bad idea. To quote Donald Knuth, premature optimization is the root of all evil. I've found out that the expression tree compiler is quite good. My tests have shown that my expression-generated delegates were about 15% faster to execute than the ones that Daniel has got. That's better than I've expected.
Performance-wise, I came to a conclusion that simply reinforces one of my software beliefs. A well-built framework will not make user code slower. Actually, some remarkable cases are capable to make user code even faster. In my opinion, instead of optimizing your code, find a good library or framework to do the job for you. If you can't find it, do not optimize. At least not now. Considering all aspects, my expression tree solution was very satisfactory. And it was fun to code too.
Following Posts
These are the next posts that I'm planning to write on Weak Events:
- Events Part Two: Immutable event entry list;
- Events Part Three: Synchronized Events
- Events Part Four: Custom Events and unsolved problems
- Dynamic Code Generation With Lambda Expressions
Source Code
Download Custom Events source code here (v1.1 @ 2008.07.31). It contains stuff that I've not discussed yet. If you're eager to see it, feel free to delve into the code. See you soon (I hope :)).
Updated at 2009.08.03: Source code link updated.
Progress on the Block Protocol
1 year ago
1 comment:
Very good!
Please, hurry up on the post on the workaround you used w/ expression trees. ;)
Post a Comment