TGStat
TGStat
Введите текст для поиска
Расширенный поиск каналов
  • flag Russian
    Язык сайта
    flag Russian flag English flag Uzbek
  • Вход на сайт
  • Каталог
    Каталог каналов и чатов Региональные подборки Тематические подборки Платные каналы Поиск каналов
    Добавить канал/чат
  • Рейтинги
    Рейтинг каналов Рейтинг чатов Рейтинг публикаций
    Рейтинги брендов и персон
  • Аналитика
  • Поиск по публикациям
  • Мониторинг Telegram
  • Продвижение
    Реклама через Яндекс Бизнес Реклама в каналах через TGStat Agency Реклама на сайте TGStat.ru
C# Heppard

4 Jun 2025, 12:45

Открыть в Telegram Поделиться Пожаловаться

IList as Span #скорость #память

В соседнем канале снова подняли вопрос по поводу разницы в скорости итерации по List и IList. Напомню, что я уже писал про это, но давно.

Если кратко, то при итерации по IList возникает проблема с боксингом получаемого List.Enumerator, так как он кастится к IEnumerator. Это даёт 40 лишних байт аллокации. Также, вызов методов IEnumerator приводит к поиску конкретной реализации по таблице виртуальных методов (callvirt в IL). Что, как не трудно догадаться, медленно, если существует более чем одна (это важно!) имплементация IEnumerator.

С тех пор, когда я про это писал, прошло много времени. Теперь я использую другой подход для случаев, когда коллеги используют IList, а мне ну уж очень надо выпендриться бежать по нему быстро.

Этот метод есть в BCL, но является internal. Он весьма неплохо оптимизирован и используется, например, для случаев, когда нужно сделать IEnumerable.Sum. Код меня более чем устраивает, так как позволяет избавиться от аллокации (боксинг List.Enumerator) и немного поднять скорость (не использовать callvirt, про который я писал тут). В production-коде я редко встречаю собственные реализации IList, поэтому метод работает очень неплохо.

Вот код:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TryGetSpan(this IEnumerable source, out ReadOnlySpan span)
{
bool result = true;
if (source.GetType() == typeof(T[]))
{
span = Unsafe.As(source);
}
else if (source.GetType() == typeof(List))
{
span = CollectionsMarshal.AsSpan(Unsafe.As(source));
}
else
{
span = default;
result = false;
}

return result;
}

В принципе, всё весьма очевидно, кроме использования CollectionsMarshal (о нём писал тут) для случая превращения List в Span. По скорости получается плюс-минус так же, как если бы я сделал CollectionsMarshal.AsSpan, только с небольшой щепоткой unsafe в виде Unsafe.As (для быстрого каста ссылочных типов).

Если нам нужно поддержать другую реализацию IList, нужно просто добавить этот случай (source.GetType() == typeof(MyList)) в этот метод. Обратите внимание, что это extension, что позволяет использовать его весьма удобно.

Бенчмарк 'https://t.me/csharp_gepard/152?comment=1834' rel='nofollow'>тут. Результаты на картинке.

P.S.: Обратите внимание на комментарий в коде BCL (this could be changed to a cast in the future if the JIT starts to recognize it). Я понимаю это так, что чаяния разработчиков, которые хотят, чтобы компилятор выполнял код из этого метода на этапе компиляции... как минимум коллегами имеются ввиду и могут быть реализованы. Но в будущем. Наверное. Хотя, может быть, я выдаю желаемое за действительное.

3.2k 1 24 10 38
Каталог
Каталог каналов и чатов Подборки каналов Поиск каналов Добавить канал/чат
Рейтинги
Рейтинг каналов Telegram Рейтинг чатов Telegram Рейтинг публикаций Рейтинги брендов и персон
API
API статистики API поиска публикаций API Callback
Наши каналы
@TGStat @TGStat_Chat @telepulse @TGStatAPI
Почитать
Академия TGStat Исследование Telegram 2019 Исследование Telegram 2021 Исследование Telegram 2023
Контакты
Справочный центр Поддержка Почта Вакансии
Всякая всячина
Пользовательское соглашение Политика конфиденциальности Публичная оферта
Наши боты
@TGStat_Bot @SearcheeBot @TGAlertsBot @tg_analytics_bot @TGStatChatBot