I recently was sent this article where Shawn Hargreaves discusses the performance implications of using the LINQ Count() operator versus the Count property of a collection, he uses Stack<T> as an example. The article has several issues with it that I tried to address in a somewhat dismissive comment that he chose not to publish. So, I’m going to expand on the ideas in that comment a little more, and hopefully do so in a more respectful tone. I want to make it clear I am not attacking him at all. I applaud anyone willing to spend time investigating fundamentals of .NET that we often take for granted. Nor am I attacking his overall conclusion that we should know what we’re doing when we use abstractions like LINQ. What I’m not happy with is how he got there. He feels that we should be careful because LINQ is much slower, which firstly I don’t agree with, and secondly I don’t think that’s necessarily a good reason to avoid it even if it is.
So, I’ll call out each of his major problems and discuss them in a bit more detail. Here he’s discussing the differences between the Count() and Stack<T>.Count:
Is it:
1. No difference: both return the number of elements on the specified stack
2. The first is a property of Stack<T>, while the second is a LINQ extension method
3. The first uses 3 IL instructions, while the second uses 34
4. The first runs in constant time, while the second is O(n)
5. The second version generates garbage, while the first does not
Answer: all of the above.
No difference: both return the number of elements on the specified stack
Well this is actually wrong. Stack<T>.Count does indeed do this, but the LINQ Count() returns the number of items in an abstract
IEnumerable<T>. In this special case that is indeed the stack, but it need not be. We could count the number of items in the stack which have a text of “Sanity”, which Stack<T>.Count would not be able to help us with. Count() then gives us a higher level of abstraction than Stack<T>.Count. Now, abstractions are not always a good thing, you can easily get leaky abstractions which can cause problems.
But one nice thing about this abstraction is that it can operate on iterators, and thus often save processing time that is not required or not waste memory storing items that are not needed.
The first is a property of Stack<T>, while the second is a LINQ extension method
I don’t think this is a complaint to be honest, but since we’re looking at performance let me just point out that the overhead of calling a LINQ extension method is virtually nothing. They are baked in at compile time and are static calls. Static calls are marginally faster than virtual method calls because they don’t have to go through a vtable lookup. When compared with non-virtual calls (like the Count property on Stack<T>) they would be about the same speed or very slightly faster.
The first uses 3 IL instructions, while the second uses 34
So what? Firstly, do we know how those IL instructions wind up on the machine? It may be 3 vs 34 IL instructions and 5 vs 15 machine instructions for all we know, or 1 vs 250. Even if there was a 1 to 1 mapping between IL and machine instructions the performance difference would be minimal. An Intel Core i7 Extreme 965EE executes just over 76 billion instructions per second. So, the difference between these two would be about a 400 millionths of a second, about 4 shakes, or about how long fusion takes in an exploding hydrogen bomb.
So, unless you’re running this in a loop at least a million times, those 31 instructions aren’t going to make a huge difference. And yes, I do know that it’d be likely a lot more than 31 machine instructions, but even at an order of magnitude more, it still wouldn’t be worth worrying about unless you were in a very tight loop which sat in the hot path of your application.
The first runs in constant time, while the second is O(n)
Yes, this could indeed become a problem if your application was dealing with hundreds of thousands of items in your stack. In such a situation I would have to ask why you were keeping so many items in a stack though. Secondly, whilst Stack<T>.Count does indeed run in constant time, that is not a given for all implementations of ICollection.Count. For example if you look at the concurrent collections in the Task Parallel Library, their Count properties do not run in O(1), but in O(N).
However, this complaint is also completely misinformed. LINQ’s Count() operator actually checks to see if it’s dealing with an ICollection and if so, it calls the Count property. So, in fact LINQ’s Count() runs in O(1) time wherever possible. Oh, and in such a case, it does it in only 10 IL instructions too!
The second version generates garbage, while the first does not
I have no idea what the author is referring to when he talks about generating garbage here. As I have shown, Count() is very nearly as efficient as calling Stack<T>.Count and also offers a level of abstraction higher than collections. Since it operates on iterators it can be vastly more efficient than a collection which has sparse items of interest, by which I mean we loaded lots of items into it, and then filter for a small subset which we want. What Shawn is discussing here is the fact that it generates objects on the heap, a serious concern for game developers like him. Happily, if the item being Count()ed is an ICollection, it won't do any such thing, and for most of the rest of us it probably doesn't matter that much.
I don’t know about you, but an efficient abstraction offering those kinds of advantages is a great idea in my book, not garbage!
Conclusion
So, do I think you should always use Count() instead of the Count property? Of course not! If you know you are dealing with a Stack<T>, then you should use its Count property. Any collection is likely to implement Count in the manner most effective for it. The Count() can approach the efficiency of the Count property, but since it’s best-case is to invoke the Count property, it is marginally more efficient to just use that property directly.
However, often it’s worth our while to operate at a higher level of abstraction. In such a case use Count() and it’s friends without guilt, they’re actually pretty damn quick. In short, don’t prematurely optimize.
Technorati Tags:
LINQ,
Performance