views:

380

answers:

2

What is the impact on memory usage of using lots of iterators in C#? Let's assume a program that performs thousands of foreach loops -- does each loop allocate a temporary object on the heap via a call to GetEnumerator? Does the CLR perform any kind of optimization (e.g. stack allocation of IEnumerator objects)? Or is this simply not a significant enough issue to even worry about?

+11  A: 

It's simply not significant enough to worry about in most cases. As Eric points out, there may be some cases where it's important, but they're pretty few and far between in my experience.

If you're doing hundreds of thousands of foreach loops, presumably you're doing actual work within those loops. That will almost certainly be far more important than the iterators themselves.

Note that using foreach over an array (known to be an array at compile time, that is) doesn't use IEnumerable<T> anyway - it uses direct indexing. I wouldn't change my code based on this though.

As ever, if you're concerned about performance you need to measure it and profile it. Bottlenecks are almost never where you expect them to be.

Jon Skeet
Though that is, as always, excellent advice Jon, our performance testing did show a significant performance impact from heap allocation/garbage collection of iterators in some real-world scenarios. That's why List<T>'s enumerator is an evil mutable value type rather than a reference type. But of course the key phrase there is "our performance testing" -- as you note, it is foolish to make performance decisions without data.
Eric Lippert
Ah yes, the evil mutable `List<T>` iterators - I seem to remember a newsgroup post with what looked like a reasonable piece of code but which gave some very odd results due to exactly that design decision :) I'll edit the answer anyway though... blanket claims of "it's not significant" are rarely a good idea.
Jon Skeet
+2  A: 

Often the compiler can optimize a foreach loop into a simple loop, which only requires an index variable on the stack (or in a processor register).

If an iterator still is used, most of them are structures, so they are just allocated on the stack instead of on the heap.

The few iterator that are classes are still quite small and fast. You could create millions of them without a noticable impact.

Guffa
Sounds great... Quick follow-up question: If I implement the IEnumerable interface, by definition the GetEnumerator method returns an IEnumerator. If my custom iterator is a struct, I'm assuming it would therefore be returned as a boxed object?
Jen
Jen, _carefully_ look at how List<T> does it, and you'll see how to avoid the boxing. The trick is that *the "foreach" loop does not actually require GetEnumerator to return IEnumerator*. As long as it returns something that has the right properties and methods, you're golden. You can thereby avoid the boxing by having a public GetEnumerator that returns a struct, and an explicit IEnumerable<T>.GetEnumerator that returns a boxed struct.
Eric Lippert
But again, as Jon says, don't do this goofy thing unless you have solid data that creating enumerators on the heap is a major cause of a real-world, customer-impacting performance problem. Mutable value types cause all kinds of strange problems that are expensive to debug and hard to understand.
Eric Lippert
It's also worth mentioning that any `IEnumerable`/`IEnumerator` implementations created by the C# compiler from iterator methods (with `yield return` statements etc) are typically classes, although that's not absolutely guaranteed by the spec.
Jon Skeet
@Eric: Presumably the static type of the expression used in the `foreach` expression has to be `List<T>` rather than (say) `IList<T>` otherwise the compiler won't know that there's another `GetEnumerator` call returning a struct, and will just have to use the normal `IEnumerable<T>.GetEnumerator` call... so you potentially get different performance characteristics (in terms of garbage collection) *and* different actual behaviour (in terms of mutability/copying) based on the static type of the variable in question.
Jon Skeet
@Eric: If the value that's enumerated is a ref type, when it's encapsulated by a value type, will it be like an automatic cleanup of the value without the GC? I am confused how a ref type value encapsulated in a value type would be cleaned up? Is this the performance gain you get in this iterator technique?
Joan Venge
@Joan: If the value that is enumerated is a reference type, you only deal with the references. The objects themselves stay in the heap, and as there are no new instance created, there is nothing to clean up.
Guffa