简体   繁体   English

为什么是List <T> .Enumerator比我的实现更快?

[英]Why is List<T>.Enumerator faster than my implementation?

I've found myself in a position where I have to roll my own dynamic array implementation, due to various large performance benefits (in my case). 由于各种大的性能优势(在我的情况下),我发现自己处于一个我必须推出自己的动态数组实现的位置。 However, after creating an enumerator for my version, and comparing the efficiency with the one List uses, I'm a bit bewildered; 但是,在为我的版本创建一个枚举器,并将效率与List使用的比较后,我有点困惑; the List one is aproximately 30-40% faster than my version, even though it's much more complex. List one比我的版本大约快30-40%,尽管它要复杂得多。

Here's the important part of the List enumerator implementation: 这是List枚举器实现的重要部分:

public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
{
    private List<T> list;
    private int index;
    private int version;
    private T current;
    internal Enumerator(List<T> list)
    {
        this.list = list;
        this.index = 0;
        this.version = list._version;
        this.current = default(T);
        return;
    }

    public bool MoveNext()
    {
        List<T> list;
        list = this.list;
        if (this.version != list._version)
        {
            goto Label_004A;
        }
        if (this.index >= list._size)
        {
            goto Label_004A;
        }
        this.current = list._items[this.index];
        this.index += 1;
        return 1;
        Label_004A:
        return this.MoveNextRare();
    }

    public T Current
    {
        get {  return this.current; }
    }
}

And here's my very barebone version: 这是我的准系统版本:

internal struct DynamicArrayEnumerator<T> : IEnumerator<T> where T : class
{
     private readonly T[] internalArray;
     private readonly int lastIndex;
     private int currentIndex;

     internal DynamicArrayEnumerator(DynamicArray<T> dynamicArray)
     {
          internalArray = dynamicArray.internalArray;
          lastIndex = internalArray.Length - 1;
          currentIndex = -1;
     }

     public T Current
     {
          get { return internalArray[currentIndex]; }
     }

     public bool MoveNext()
     {
          return (++currentIndex <= lastIndex);
     }
}

I know this is micro-optimization, but I'm actually interested in understanding why the List enumerator is so much faster than mine. 我知道这是微优化,但我真的很有兴趣理解为什么List枚举器比我的快得多。 Any ideas? 有任何想法吗? Thanks! 谢谢!

Edit: As requested; 编辑:按要求; the DynamicArray class (the relevant parts): The enumerator is an inner class in this. DynamicArray类(相关部分):枚举器是一个内部类。

public struct DynamicArray<T> : IEnumerable<T> where T : class
{
    private T[] internalArray;
    private int itemCount;

    internal T[] Data
    {
        get { return internalArray; }
    }

    public int Count
    {
        get { return itemCount; }
    }

    public DynamicArray(int count)
    {
        this.internalArray = new T[count];
        this.itemCount = 0;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new DynamicArrayEnumerator<T>(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

}

As for how I'm testing: 至于我如何测试:

 List<BaseClass> list = new List<BaseClass>(1000000);
 DynamicArray<BaseClass> dynamicArray = new DynamicArray<BaseClass>(1000000);

// Code for filling with data omitted.

   int numberOfRuns = 0;
   float p1Total = 0;
   float p2Total = 0;
   while (numberOfRuns < 100)
   {
        PerformanceAnalyzer p1 = new PerformanceAnalyzer(() =>
        {
             int u = 0;
             foreach (BaseClass b in list)
             {
                  if (b.B > 100)   // Some trivial task
                      u++;
             }
        });
        p1.ExecuteAndClock();
        p1Total += p1.TotalElapsedTicks;

        PerformanceAnalyzer p2 = new PerformanceAnalyzer(() =>
        {
             int u = 0;
             foreach (BaseClass b in dynamicArray)
             {
                  if (b.B > 100)  // Some trivial task
                       u++;
             }
        });
        p2.ExecuteAndClock();
        p2Total += p2.TotalElapsedTicks;

        numberOfRuns++;
    }

    Console.WriteLine("List enumeration: " + p1Total / totalRuns + "\n");
    Console.WriteLine("Dynamic array enumeration: " + p2Total / totalRuns + "\n");

The PerformanceAnalyzer class basically starts a Stopwatch, execute the supplied Action delegate, and then stop the Stopwatch afterwards. PerformanceAnalyzer类基本上启动一个秒表,执行提供的Action委托,然后停止秒表。

Edit 2 (Quick answer to Ryan Gates): There's a few reasons why I would want to roll my own, most importantly I need a very fast RemoveAt(int index) method. 编辑2(对Ryan Gates的快速回答):有几个原因我想要自己动手,最重要的是我需要一个非常快的RemoveAt(int index)方法。

Since I don't have to worry about the order of the list elements in my particular case, I can avoid the .Net built-in list's way of doing it: 由于我不必担心在我的特定情况下列表元素的顺序,我可以避免.Net内置列表的方式:

public void RemoveAt(int index)
{
    T local;
    if (index < this._size)
    {
        goto Label_000E;
    }
    ThrowHelper.ThrowArgumentOutOfRangeException();
Label_000E:
    this._size -= 1;
    if (index >= this._size)
    {
        goto Label_0042;
    }
    Array.Copy(this._items, index + 1, this._items, index, this._size - index);
Label_0042:
    this._items[this._size] = default(T);
    this._version += 1;
    return;
}

And instead using something along the lines of: 而是使用以下内容:

public void RemoveAt(int index)
{
     // overwrites the element at the specified index with the last element in the array and decreases the item count.
     internalArray[index] = internalArray[itemCount];  
     itemCount--;
}

Potencially saving enormous amounts of time in my case, if say the first 1000 elements in a long list have to be removed by index. 在我的情况下可以节省大量的时间,如果说长列表中的前1000个元素必须通过索引删除。

Okay, aside from benchmarking problems, here's how you can make your DynamicArray class more like List<T> : 好的,除了基准测试问题之外,以下是如何使您的DynamicArray类更像List<T>

public DynamicArrayEnumerator<T> GetEnumerator()
{
    return new DynamicArrayEnumerator<T>(this);
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
    return GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

Now, code which knows it's working with a dynamic array can iterate with a DynamicArrayEnumerator<T> without any boxing, and without virtual dispatch . 现在, 知道它使用动态数组的代码可以使用DynamicArrayEnumerator<T>进行迭代而无需任何装箱,也无需虚拟分派 This is exactly what List<T> does. 这正是List<T>所做的。 The compiler notices when a type implements the pattern in a custom manner, and will use the types involved instead of the interfaces. 当类型以自定义方式实现模式时,编译器会注意到,并将使用所涉及的类型而不是接口。

With your current code, you're getting no benefit from creating a struct - because you're boxing it in GetEnumerator() . 使用您当前的代码,您将无法从创建struct获益 - 因为您在GetEnumerator()中将其装箱。

Try the above change and fix the benchmark to work for longer. 尝试上述更改修复基准测试以延长工作时间。 I'd expect to see a big difference. 我希望看到一个很大的不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM