alexeyfv

Опубликовано

- 2 мин чтения

Что такое ReadOnlySpan<T> в C# и насколько он быстрый

C# Производительность
img of Что такое ReadOnlySpan<T> в C# и насколько он быстрый

Ранее я уже писал статью о самом быстром способе извлечения подстроки в C#. Теперь подробно расскажу о Span. Недавно Microsoft выпустила платформу .NET 8, в которой появилось несколько новых методов расширения для ReadOnlySpan<T>. Поэтому я решил сравнить производительность методов MemoryExtensions и аналогов в классе string.

Бенчмарк

Все тесты используют JSON-файл с массивом строк из 135 892 элементов. Каждый элемент — это права доступа для разных папок. Строки имеют следующий шаблон:

   <permission> for Folder: \\server-name\path\to\folder\

Например:

   DENY Permission for Folder: \\server-name\path\to\folder\

Я не могу публиковать сам файл, так что придётся поверить на слово. :)

В этом бенчмарке рассмотрены 8 методов:

stringReadOnlySpan<T>
ContainsContains
StartsWithStartsWith
IndexOfIndexOf
ReplaceReplace
SplitSplit
ToLowerInvariantToLowerInvariant
TrimTrim
SubstringSlice

Также я учитываю сценарий “Span + Аллокация”, когда используется метод ToString после работы со Span, что приводит к созданию строки в памяти.

Как обычно, я использовал библиотеку BenchmarkDotNet. Полный код тестов доступен здесь.

Результаты

Contains

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String615.701
ReadOnlySpan<T>624.1-2%01

StartsWith

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String56681.1082
ReadOnlySpan<T>329.6-99.4%00-100%

IndexOf

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String224182.40245
ReadOnlySpan<T>1131.6-99.5%01-99.6%

Split

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String16241.34468.7556127351
ReadOnlySpan<T>9977.2-39%01060-100%

Replace

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String3156.82019.5325353195
ReadOnlySpan<T>2663.0-16%03-100%
ReadOnlySpan<T> с аллокацией16701.5+430%2015.62253532040%

ToLowerInvariant

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String3771.32019.5325353195
ReadOnlySpan<T>3884.3+3%03-100%
ReadOnlySpan<T> с аллокацией17938.6+374%2015.62253532040%

Trim

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String687.4553
ReadOnlySpan<T>469.1-32%-99.8%
ReadOnlySpan<T> с аллокацией2968.1+332%2019.5325353195+4584565%

Substring

БенчмаркСреднее время, мксРазницаGen 0 на 1000 операцийВыделено памяти, байтРазница по памяти
String1523.8779.39784225
ReadOnlySpan<T>347.9-77%-100%
ReadOnlySpan<T> с аллокацией1694.5+11%779.397842250%

Выводы

ReadOnlySpan<T> без аллокации

Сравнение производительности ReadOnlySpan

Производительность ReadOnlySpan

Почти все методы расширения для ReadOnlySpan<T> работают быстрее, чем аналоги для string. Исключение — метод ToLower, потому что он копирует символы в новый буфер.

ReadOnlySpan<T> с аллокацией

Сравнение производительности ReadOnlySpan с аллокацией

Производительность ReadOnlySpan с аллокацией

Методы string.Replace, string.ToLower, string.TrimEnd, string.Substring работают быстрее комбинации ReadOnlySpan<T> и вызова ToString. Вероятно, это связано с тем, что стандартные строковые методы используют более эффективные внутренние механизмы, например Buffer.Memmove.

Потребление памяти

Методы на основе Span не создают мусора и не вызывают сборку мусора Gen 0. Строковые методы, особенно Split, Replace и ToLower, выделяют гораздо больше памяти.

Однако если в конце операций на Span всё равно происходит создание строки (ToString), то преимущества по памяти теряются.

Количество Gen0 на 1000 операций:

КатегорияStringSpanSpan + Аллокация
Contains00-
StartsWith00-
IndexOf00-
Split4468.750-
Replace2019.5302015.62
ToLower2019.5302015.62
Trim1333.3302019.53
Substring779.30779.3

Память, выделенная (МБ):

КатегорияStringSpanSpan + Аллокация
Contains0.000.00-
StartsWith0.000.00-
IndexOf0.000.00-
Split53.530.00-
Replace24.180.0024.18
ToLower24.180.0024.18
Trim0.000.0024.17
Substring9.330.009.33