4

I'm trying to take advantage of iterators in C# to clean up some spatial queries on objects in a game I'm making.

Here's what I'm doing currently:

    public struct ObjectInfo
    {
        public int x, y;
        public int Type;
        public int hp;
    }

    public static IEnumerable<ObjectInfo> NearbyObjects(int x, int y, int distance)
    {
        // Not actually what I'm doing, but for simplicity...
        for (int i = 0; i < 10; ++ i)
        {
            yield return new ObjectInfo();
        }
    }

    public static void Explode()
    {
        foreach (ObjectInfo o in NearbyObjects(0, 0, 1))
        {
            o.hp = 0;
        }
    }

This works great except I think there is some boxing/unboxing going on. The CLRProfiler seems to verify this as I'm seeing allocations happening in my Explode method. I'd very much like to avoid any allocations after startup so I'm not triggering the garbage collector in the middle of a level or something. Is there some way I can keep this syntax while avoiding any allocation? Maybe by implementing my own iterators or something?

hahanoob
  • 43
  • 3

3 Answers3

2

Your code does not do any boxing.

Boxing only happens when you put a struct into an object field.
Since your code is fully strongly-typed and doesn't have any object fields, it doesn't box.

Calling an iterator method will create a new iterator object (which implements both IEnumerable<T> and IEnumerator<T>), but should not result in any other (behind-the-scenes) allocations.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • I see. I thought maybe my ObjectInfo was being stuffed into an object by the iterator somehow. Can I make my own iterator that's also a struct then? I'll look over the link you provided and see if I can figure it out from that. – hahanoob May 17 '11 at 01:53
  • It seems it's not possible to use yield return without allocating a class object behind the scenes. That's unfortunate. – hahanoob May 17 '11 at 02:20
  • @haha: How could you return an `IEnumerable` without creating an object? – SLaks May 17 '11 at 02:38
  • I don't necessarily need to return a class object implementing IEnumerable though? I could just return a struct with the GetEnumerator method. It seems like the state machine that is generated by yield return is what's causing the allocation. – hahanoob May 17 '11 at 10:57
  • @haha: That wouldn't help. Since the method returns `IEnumerable` (not the private struct type), it would need to be boxed. – SLaks May 17 '11 at 12:34
1

Boxing/unboxing will happen with structs.

See here: http://msdn.microsoft.com/en-us/library/aa664476(v=vs.71).aspx

Also here: Is there Boxing/Unboxing when casting a struct into a generic interface?

Community
  • 1
  • 1
zsalzbank
  • 9,685
  • 1
  • 26
  • 39
0

The allocations in your Explode method are due to the internal workings of the iterator itself, not due to boxing.

Every iterator block is implemented by the compiler using a reference class to maintain the state which is used in the course of iterating over the IEnumerable, in your case with foreach. Normally the allocation of this class is not noticed as a problem because the iterator iterates over a large collection or the client who is iterating is doing substantial work of their own or allocation that overwhelm the iterators allocation.

But if your collection is small (as yours is), and the foreach is very fast and has no allocations of its own, all that will be left is the allocation of the iterator state class.

To solve this performance problem you can simply reorganize your code to use for instead of foreach whereever the profiler tells you a lot of memory is being allocated in an iterator block. It's usually a modest change and the performance boost if measurable is then worth it.

Rick Sladkey
  • 33,988
  • 6
  • 71
  • 95