Good, fast, pretty code: How to choose?
I have a piece of code that does something on types. It is a whole complex thing that does a lot of stuff. And the code is really ugly, here is a small part from ~180 lines method.
The problem would have been much simpler if we could only switch on types, which is effectively what I want to do here. As it stands, however, the JIT is actually going to replace all those if statements with pointer comparisons to the method table, so this is pretty fast. Unpleasant to read, but really fast.
I decided to see what it would take to create a more readable version:
public class TypeCounter { public int Long; public Dictionary<Type, Action<object>> Actions; public TypeCounter() { Actions = new Dictionary<Type, Action<object>> { [typeof(long)] = o => Long++, [typeof(int)] = o => Long++, }; } public void Dic(object instance) { if (instance == null) return; Action<object> value; if (Actions.TryGetValue(instance.GetType(), out value)) value(instance); } public void Explicit(object instance) { if (instance is int) Long++; else if (instance is long) Long++; } }
This is just a simpler case of the code above, focused on just the two options. The dictionary version is much more readable, especially once you get to more than a couple of types. The problem, I have tested this on both this two types option as well as 10 types, and in all cases, the explicit version is about twice as fast.
So this rules it out, and we are left with the sad looking code that can run.
Comments
there are exceptions for readability, and its performance and special edge cases. even microsoft's native methods are not friendly readable. the end user don't really care how code written on the server side as long as its working reliable and fast.
Maybe this will become real, someday : http://bartdesmet.net/blogs/bart/archive/2009/07/12/bart-s-control-library-not-what-you-think-it-is-part-1.aspx Is meta-programming acceptable to transform pretty code into an efficient one ?
I'm actually surprised that
Dictionary + Action + increment
takes only twice the time of acheck type + increment
. I would have thought the overhead of a dictionary would absolutely dwarf everything.Therefor it would seem that if you actually did more something useful than just an increment, that the overhead of the dictionary would become negligible.
Btw, you mention that even in the 10 types case the explicit version is still twice as fast as the dictionary code. Does that mean that the explicit version runs pretty much in constant time, regardless of the amount of types, or that the dictionary becomes slower with more entries? In either case that would be a respectively pleasant or unpleasant surprise.
Patrick, The dictionary cost in such small dic is effectively an array access, and then you have the cost of the invoking the delegate, which is where it bit us. The explicit cost is O(N) of typePtr == 0x1234ABCD, and there is really very little cost here if N is small.
Nekresh, This is a single use function. Meta programming is going to make it even harder to understand
You could switch the delegate to an integer that would give you something to use the switch statement with. This would eliminate the cost of the delegate and get something prettier.
David, That is a very good idea. Testing this out shows comparable perormance to the explicit version
Another option would to make the containing class partial and implement this method in a separate file generated by a T4 template.
Sebastiaan, The problem is that there is different behavior for each branch. And the code is a single instance. There isn't much point in complicating it with dynamic code generation
Using a static class as a type dictionary for gives +70% better perf, and is almost as readable as the Dictionary case. Obviously this only works if you're calling code is typed rather than an object.
<pre> public class TypeCounter { ... private void SetupTypeDictionary() { StaticTypeDictionary<int>.Action = o => Long++; StaticTypeDictionary<long>.Action = o => Long++; } public void UseTypeDictionary<T>(T instance) { StaticTypeDictionary<T>.DoAction(instance); } } static class StaticTypeDictionary<T> { public static Action<T> Action { get; set; } public static void DoAction(T instance) { Action(instance); } } </pre>Sorry - dropped generic type params there.
Joskha, You can't do that, the type information isn't available at compile time
Seems like what would be needed here to be able to write SOLID, clean, and performant code is a programming environment that can "nonvirtualize" as Herb Sutter called it:
http://www.drdobbs.com/inline-redux/184403879
Joe Armstrong, one of the designers of Erlang, is quoted in "Erlang and OTP in Action" as saying "Make it work, then make it beautiful, then if you really, really have to, make it fast. 90 percent of the time, if you make it beautiful, it will already be fast. So really, just make it beautiful!"
And that, for me, answers the "how to choose" question posed in the title.
The case you're showing here seems to be in that remaining 10%, which is fair enough, but I can't help thinking that by starting with the non-beautiful (but fast) code, you've inadvertently started the discussion off pointing in the wrong direction. It turns into "how could we make this beautiful?", and -- I assume -- you already started with beautiful code that wasn't fast enough. Personally, I much prefer "here's some beautiful code, it's not fast (enough), how can we make it faster?" discussions.
Roger, If you looked into the previous posts, you might have seen that we're doing heavy performance work. So we start with code that we know is written in an optimized fashion, based on our experience. Then we profile it to see that we got it right, then we see if we can stand it ;-)
This could be implemented as a chain of responsibility.
Gary, Not efficiently, no
Hi Oren,
thank you for posting an example of contradicting optimizations. This seems to be the worst part of a programmer's job (despite variable naming).
Normally I set my priorities in this order: 1. Write correct code. 2. Write readable code. 3. Write fast code.
Of course you can set your own priorities, but if speed is a real burning issue for your product, maybe you should make a larger step and implement the bottlenecks in a faster language (Assembler).
Eric Lippert emphasizes how pointless a microoptimization in VBScript code is in https://blogs.msdn.microsoft.com/ericlippert/2003/10/17/how-bad-is-good-enough/
Karsten, do you really think that implementing something in assembler is going to result in cleaner code than the one above?
If it is only for at that one place a language change is nonsense. I'm just thinking that if you are in a situation, where fast code is more important than readable or correct code, you should check the amount and severity of your bottlenecks and consider implementing them in a faster language.
I can only guess since I never programmed in assembler or called assembler code from C#, but I can imagine that a well written library call might be better™ than widespread code smell.
Karsten, That one place is going to require maintenance. And you make a very bad assumption here, that I can't get comparable speed with the same platform, with much more readable semantics.
Note that there is also a non trivial cost to making pinvoke calls that you also need to take into account
In ugly but very fast code an option is to put a big comment at the top of the file, effectively warning people that this code is performance critical, hence it being unreadable but very fast, and warning others not to refactor this class without fully performance testing the result. Then potentially an explanation of how the thing works.
@Karsten, also there are a few misconceptions about why a "managed" language is slower that say C++ or C. There is nothing intrinsically bad about managed language that makes them slower. The real question you have to ask is: "Is the library you are using fast enough?" or "Does the JIT make the best code choices out there?" ... for both question there is usually a "No" response.
For most general use cases, it is usually the inverse. Managed code (well written) is faster tan code you can crunch in C++/C in the same amount of hours it took you to write the managed code. However, once you figure out there is an issue out there, like the JIT not performing an optimization that is pretty common in C/C++ like understanding the idiomatic rotate bits (https://github.com/dotnet/coreclr/issues/1619 ) then you are onto something. But, as shown in that example, you can still get those optimizations in place if they are really worth it ;)
Interesting. I just watched a video on C#7 (possible) features and switching on types was the first thing they talked about. For a good 20 minutes or so.
Douglas, Yes, the major question is what the implementation performance of that will be
Comment preview