TGViewer
C# Heppard C# Heppard @csharp_gepard · 1.65K subscribers
Post #152 3.35K
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). Я понимаю это так, что чаяния разработчиков, которые хотят, чтобы компилятор выполнял код из этого метода на этапе компиляции... как минимум коллегами имеются ввиду и могут быть реализованы. Но в будущем. Наверное. Хотя, может быть, я выдаю желаемое за действительное.
  • 👍 24
  • 🔥 10
  • 👀 2
  • ❤ 1
  • 🐳 1
More from @csharp_gepard
  1. Mar 13, 2026Ozon Tech Community .NET Meetup 24 марта, лофт Casa Picassa, 19:00 Ищем способы бороться с…
  2. Jan 20, 2026Ещё одна история про память #отдых Судя по комментариям специалистов и ценам на RAM в мага…
  3. Nov 25, 2025FastEnum #скорость Иногда нам нужно не просто быстро, а очень быстро. В этот момент в дело…
  4. Nov 20, 2025UnsafeAccessor #скорость Иногда возникает странное желание изменять приватные поля в класс…
  5. Nov 17, 2025Latency numbers every programmer should know Я всё время теряю эти циферки, особенно когда…
  6. Oct 29, 2025Dictionary.TryGetValue #скорость Уж сколько лет прошло и, вроде, все это знают. Но, тем не…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →