Allowing iteration without generating any garbage

前端 未结 7 1200
时光取名叫无心
时光取名叫无心 2021-02-01 04:49

I have the following code in an object pool that implements the IEnumerable interface.

public IEnumerable ActiveNodes
{
    get
    {
        for (int i         


        
相关标签:
7条回答
  • 2021-02-01 05:07

    Does it have to be IEnumerable? Will refactoring to an array with good old indexed acecss help?

    0 讨论(0)
  • 2021-02-01 05:09

    Also, you can have a pool of preallocated enumerators. Think about how many simultaneous enumerations you want to support.

    The garbage collection overhead will go, at the expense of extra memory consumption. Speed vs. memory optimization dilemma in its purest form.

    0 讨论(0)
  • 2021-02-01 05:13

    You could implement your own IEnumerator class which will enumerate over these active nodes.

    Now if you can guarantee that only one client will be enumerating over the active nodes at any one time, your class can cache this class so only a single instance exists. Then no garbage will need collecting. The call to ActiveNodes will call reset to start enumerating from the start.

    This is a dangerous assumption to make, but if you are optimising you may be able to consider it

    If you have multiple clients enumerating over these nodes at any time, then each will need its own instance of IEnumerator to be able to store their current cursor position in the collection. In which case these will need to be created and collected with each call - and you may as well stick with your original design.

    0 讨论(0)
  • 2021-02-01 05:15

    Since XNA for XBox also works over the Compact Framework (and I suspect that's what you're working on given the hints you've given(1)), we can trust the XNA devs to teach us exactly when foreach creates garbage.

    To quote the most relevant point (although the entire article's worth reading):

    When doing a foreach over a Collection<T> an enumerator WILL be allocated.

    When doing a foreach over most other collections, including as arrays, lists, queues, linked lists, and others:

    • if the collections are used explicitly, an enumerator will NOT be allocated.
    • if they are cast to interfaces, an enumerator WILL be allocated.

    So, if _pool is a List, array or similar and can afford to, you can either return that type directly or cast the IEnumerable<T> to the respective type to avoid garbage during the foreach.

    As some additional reading, Shawn Hargreaves can have some useful additional information.

    (1) 60 calls per second, Compact Framework, can't go down to native code, 1MB of allocation before a GC is triggered.

    0 讨论(0)
  • 2021-02-01 05:19

    Iterating items will in any 'normal' design usually result in the creation of a new enumerable object. Creating and disposing objects is very fast, so only in very special scenarios (where low latency is the top most priority) garbage collections could (I say 'could') be a problem.

    A design without garbage is possible by returning structures that don't implement IEnumerable. The C# compiler can still iterate such objects, because the foreach statement uses duck typing. .NET's List<T>, for instance, takes this approach.

    When using foreach over both an array and List<T>, no garbage will be generated. When using foreach on an array, C# will transform the operation to a for statement, while List<T> already implements a struct enumerator, causing the foreach to produce no garbage.

    Here is a struct enumerable and struct enumerator. When you return the enumerable, the C# compiler can foreach over it:

    public struct StructEnumerable<T>
    {
        private readonly List<T> pool;
    
        public StructEnumerable(List<T> pool)
        {
            this.pool = pool;
        }
    
        public StructEnumerator<T> GetEnumerator()
        {
            return new StructEnumerator<T>(this.pool);
        }
    }
    

    Here is the StructEnumerator:

    public struct StructEnumerator<T>
    {
        private readonly List<T> pool;
        private int index;
    
        public StructEnumerator(List<T> pool)
        {
            this.pool = pool;
            this.index = 0;
        }
    
        public T Current
        {
            get
            {
                if (this.pool == null || this.index == 0)
                    throw new InvalidOperationException();
    
                return this.pool[this.index - 1];
            }
        }
    
        public bool MoveNext()
        {
            this.index++;
            return this.pool != null && this.pool.Count >= this.index;
        }
    
        public void Reset()
        {
            this.index = 0;
        }
    }
    

    You can simply return the StructEnumerable<T> as follows:

    public StructEnumerable<T> Items
    {
        get { return new StructEnumerable<T>(this.pool); }
    }
    

    And C# can iterate over this with a normal foreach:

    foreach (var item in pool.Items)
    {
        Console.WriteLine(item);
    }
    

    Note that you can't LINQ over the item using System.Linq.Enumerable> You need the IEnumerable<T> interface for that, and that involves creating enumerators and, therefore, garbage collection. You could, of course, build your own LINQ extension methods, but that will unlikely help, because that will often still result in new objects being created (when closures are being generated for used delegates).

    0 讨论(0)
  • 2021-02-01 05:19

    Dont be afraid of those tiny garbage objects. The ActiveNodes in the pool will (should) be much more costly. Therefore, if you get rid of recreating them it should be sufficient.

    @Edit: if you are made to use a managed platform and really want to archieve a zero-garbage state, disclaim the usage of the pool in a foreach loop and iterate over it in another manner, possibly utilizing an indexer. Or consider creating a list of potential nodes and return that instead.

    @Edit2: Of course, implementing IEnumerator und using Current(), Next() and so on, would work as well.

    0 讨论(0)
提交回复
热议问题