CSHARP_GEPARD Telegram 152
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👀21🐳1



tgoop.com/csharp_gepard/152
Create:
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

View MORE
Open in Telegram


Telegram News

Date: |

Although some crypto traders have moved toward screaming as a coping mechanism, several mental health experts call this therapy a pseudoscience. The crypto community finds its way to engage in one or the other way and share its feelings with other fellow members. Private channels are only accessible to subscribers and don’t appear in public searches. To join a private channel, you need to receive a link from the owner (administrator). A private channel is an excellent solution for companies and teams. You can also use this type of channel to write down personal notes, reflections, etc. By the way, you can make your private channel public at any moment. Unlimited number of subscribers per channel A vandalised bank during the 2019 protest. File photo: May James/HKFP. Telegram message that reads: "Bear Market Screaming Therapy Group. You are only allowed to send screaming voice notes. Everything else = BAN. Text pics, videos, stickers, gif = BAN. Anything other than screaming = BAN. You think you are smart = BAN.
from us


Telegram C# Heppard
FROM American