tgoop.com/csharp_gepard/152
Last Update:
IList as Span #скорость #память
В соседнем канале снова подняли вопрос по поводу разницы в скорости итерации по List<T>
и IList<T>
. Напомню, что я уже писал про это, но давно.
Если кратко, то при итерации по IList
возникает проблема с боксингом получаемого List<T>.Enumerator
, так как он кастится к IEnumerator<T>
. Это даёт 40 лишних байт аллокации. Также, вызов методов IEnumerator<T>
приводит к поиску конкретной реализации по таблице виртуальных методов (callvirt
в IL). Что, как не трудно догадаться, медленно, если существует более чем одна (это важно!) имплементация IEnumerator<T>
.
С тех пор, когда я про это писал, прошло много времени. Теперь я использую другой подход для случаев, когда коллеги используют IList
, а мне ну уж очень надо выпендриться бежать по нему быстро.
Этот метод есть в BCL, но является internal. Он весьма неплохо оптимизирован и используется, например, для случаев, когда нужно сделать IEnumerable<T>.Sum
. Код меня более чем устраивает, так как позволяет избавиться от аллокации (боксинг List<T>.Enumerator
) и немного поднять скорость (не использовать callvirt
, про который я писал тут). В production-коде я редко встречаю собственные реализации IList
, поэтому метод работает очень неплохо.
Вот код:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TryGetSpan<T>(this IEnumerable<T> source, out ReadOnlySpan<T> span)
{
bool result = true;
if (source.GetType() == typeof(T[]))
{
span = Unsafe.As<T[]>(source);
}
else if (source.GetType() == typeof(List<T>))
{
span = CollectionsMarshal.AsSpan(Unsafe.As<List<T>>(source));
}
else
{
span = default;
result = false;
}
return result;
}
В принципе, всё весьма очевидно, кроме использования
CollectionsMarshal
(о нём писал тут) для случая превращения List<T>
в Span<T>
. По скорости получается плюс-минус так же, как если бы я сделал CollectionsMarshal.AsSpan
, только с небольшой щепоткой unsafe в виде Unsafe.As
(для быстрого каста ссылочных типов).Если нам нужно поддержать другую реализацию
IList
, нужно просто добавить этот случай (source.GetType() == typeof(MyList)
) в этот метод. Обратите внимание, что это extension, что позволяет использовать его весьма удобно. Бенчмарк тут. Результаты на картинке.
P.S.: Обратите внимание на комментарий в коде BCL (
this could be changed to a cast in the future if the JIT starts to recognize it
). Я понимаю это так, что чаяния разработчиков, которые хотят, чтобы компилятор выполнял код из этого метода на этапе компиляции... как минимум коллегами имеются ввиду и могут быть реализованы. Но в будущем. Наверное. Хотя, может быть, я выдаю желаемое за действительное.BY C# Heppard

Share with your friend now:
tgoop.com/csharp_gepard/152