Чтобы добавить поддержку foreach
в пользовательскую коллекцию, необходимо реализовать IEnumerable
.Массивы, тем не менее, отличаются тем, что по существу они компилируются в цикл for на основе диапазона, который на намного быстрее, чем при использовании IEnumerable.Простой тест подтверждает, что:
number of elements: 20,000,000
byte[]: 6.860ms
byte[] as IEnumerable<byte>: 89.444ms
CustomCollection.IEnumerator<byte>: 89.667ms
Тест:
private byte[] byteArray = new byte[20000000];
private CustomCollection<byte> collection = new CustomCollection<T>( 20000000 );
[Benchmark]
public void enumerateByteArray()
{
var counter = 0;
foreach( var item in byteArray )
counter += item;
}
[Benchmark]
public void enumerateByteArrayAsIEnumerable()
{
var counter = 0;
var casted = (IEnumerable<byte>) byteArray;
foreach( var item in casted )
counter += item;
}
[Benchmark]
public void enumerateCollection()
{
var counter = 0;
foreach( var item in collection )
counter += item;
}
И реализация:
public class CustomCollectionEnumerator : IEnumerable<T> where T : unmanaged
{
private CustomCollection<T> _collection;
private int _index;
private int _endIndex;
public CustomCollectionEnumerator( CustomCollection<T> collection )
{
_collection = collection;
_index = -1;
_endIndex = collection.Length;
}
public bool MoveNext()
{
if ( _index < _endIndex )
{
_index++;
return ( _index < _endIndex );
}
return false;
}
public T Current => _collection[ _index ];
object IEnumerator.Current => _collection[ _index ];
public void Reset() { _index = -1; }
public void Dispose() { }
}
public class CustomCollection<T> : IEnumerable<T> where T : unmanaged
{
private T* _ptr;
public int Length { get; private set; }
public T this[ int index ]
{
[MethodImpl( MethodImplOptions.AggressiveInlining )]
get => *_ptr[ index ];
[MethodImpl( MethodImplOptions.AggressiveInlining )]
set => *_ptr[ index ] = value;
}
public IEnumerator<T> GetEnumerator()
{
return new CustomCollectionEnumerator<T>( this );
}
}
Поскольку массивы получают специальную обработку от компилятора, ониоставьте IEnumerable
коллекции в пыли.Поскольку C # в значительной степени фокусируется на безопасности типов, я могу понять, почему это так, но это все еще несет абсурдное количество накладных расходов, особенно для моей пользовательской коллекции, которая перечисляет в точно так же, как , как массив,Фактически, моя пользовательская коллекция работает быстрее, чем байтовый массив в диапазоне, основанном на цикле for, поскольку она использует арифметику указателей для пропуска проверок диапазона массива CLR.
Так что мой вопрос: Есть ли способнастроить поведение цикла foreach
так, чтобы я мог достичь производительности, сопоставимой с массивом? Может быть, с помощью встроенных функций компилятора или ручной компиляции делегата с помощью IL?
Конечно, я всегда могу вместо этого использовать диапазон, основанный на цикле. Мне просто любопытно, есть ли какой-нибудь возможный способ настроить низкоуровневое поведение цикла foreach
аналогично тому, как компилятор обрабатывает массивы.