C
C# Heppard
@csharp_gepard1.7K подп.
3.3Kпросмотров
4 июня 2025 г.
📷 ФотоScore: 3.6K
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). Я понимаю это так, что чаяния разработчиков, которые хотят, чтобы компилятор выполнял код из этого метода на этапе компиляции... как минимум коллегами имеются ввиду и могут быть реализованы. Но в будущем. Наверное. Хотя, может быть, я выдаю желаемое за действительное.
3.3K
просмотров
2698
символов
Нет
эмодзи
Да
медиа

Другие посты @csharp_gepard

Все посты канала →
IList as Span #скорость #память В соседнем канале снова подн — @csharp_gepard | PostSniper