Почему List <T>.Enumerator быстрее, чем моя реализация?

Я нашел себя в положении, когда мне приходится сворачивать свою собственную динамическую реализацию массива из-за различных больших преимуществ производительности (в моем случае). Однако после создания перечислителя для моей версии и сравнения эффективности с одним из списков, я немного сбита с толку; Список один примерно на 30-40% быстрее, чем моя версия, хотя это намного сложнее.

Здесь важная часть реализации перечисления 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; }
    }
}

И вот моя очень barebone версия:

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);
     }
}

Я знаю, что это микро-оптимизация, но мне действительно интересно понять, почему перечислитель List намного быстрее моего. Есть идеи? Спасибо!

Изменить: Как просили; класс 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();
    }

}

Как я тестирую:

 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");

Класс PerformanceAnalyzer в основном запускает секундомер, выполняет предоставленный делегат действия и затем останавливает Секундомер впоследствии.

Изменить 2 (Быстрый ответ Райан Гейтс): Есть несколько причин, по которым я хотел бы свернуть свой собственный, что очень важно, мне нужен очень быстрый метод RemoveAt (int index).

Так как мне не нужно беспокоиться о порядке элементов списка в моем конкретном случае, я могу избежать использования встроенного списка .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;
}

И вместо этого используя что-то по строкам:

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--;
}

Потенциально экономя огромное количество времени в моем случае, если сказать, что первые 1000 элементов в длинном списке должны быть удалены индексом.

Ответы

Ответ 1

Хорошо, помимо проблем с бенчмаркингом, вот как вы можете сделать свой класс 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();
}

Теперь код, который знает, что он работает с динамическим массивом, может выполнять итерацию с помощью DynamicArrayEnumerator<T> без какого-либо бокса и без виртуальной отправки. Это как раз то, что делает List<T>. Компилятор замечает, когда тип реализует шаблон произвольным образом и будет использовать типы, используемые вместо интерфейсов.

С вашим текущим кодом вы не получаете никакой выгоды от создания struct - потому что вы боксируете его в GetEnumerator().

Попробуйте вышеуказанное изменение и исправьте эталон, чтобы работать дольше. Я ожидаю увидеть большую разницу.