Почему 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()
.
Попробуйте вышеуказанное изменение и исправьте эталон, чтобы работать дольше. Я ожидаю увидеть большую разницу.