В чем разница между Span<T> и Memory<T> в C# 7.2?

C# 7.2 вводит два новых типа: Span<T> а также Memory<T> которые имеют лучшую производительность по сравнению с более ранними типами C#, такими как string[],

Вопрос: в чем разница между Span<T> а также Memory<T>? Зачем мне использовать один над другим?

4 ответа

Решение

Span<T> только для стека Memory<T> можно использовать кучу.

Span<T> это новый тип, который мы добавляем в платформу для представления смежных областей произвольной памяти с характеристиками производительности на уровне T[]. Его API-интерфейсы похожи на массив, но в отличие от массивов, он может указывать либо на управляемую, либо на собственную память, либо на память, выделенную в стеке.

Memory <T> это тип, дополняющий Span<T>, Как обсуждено в его проектной документации, Span<T> тип только для стека Только для стека Span<T> делает его непригодным для многих сценариев, которые требуют хранения ссылок на буферы (представлены Span<T>) в куче, например, для подпрограмм, выполняющих асинхронные вызовы.

async Task DoSomethingAsync(Span<byte> buffer) {
    buffer[0] = 0;
    await Something(); // Oops! The stack unwinds here, but the buffer below
                       // cannot survive the continuation.
    buffer[0] = 1;
}

Для решения этой проблемы мы предоставим набор дополнительных типов, предназначенных для использования в качестве типов обмена общего назначения, представляющих, как Span <T>Диапазон произвольной памяти, но в отличие Span <T> эти типы не будут только стековыми, за счет значительных потерь производительности при чтении и записи в память.

async Task DoSomethingAsync(Memory<byte> buffer) {
    buffer.Span[0] = 0;
    await Something(); // The stack unwinds here, but it's OK as Memory<T> is
                       // just like any other type.
    buffer.Span[0] = 1;
}

В приведенном выше примере Memory <byte> используется для представления буфера. Это обычный тип и может использоваться в методах, выполняющих асинхронные вызовы. Возвращает его свойство Span Span<byte>, но возвращаемое значение не сохраняется в куче во время асинхронных вызовов, а новые значения создаются из Memory<T> значение. В некотором смысле Memory<T> это фабрика Span<T>,

Справочный документ: здесь

re: это означает, что он может указывать только на память, выделенную в стеке.

Span<T> может указывать на любую память: выделенную либо в стеке, либо в куче. Стек только природа Span<T> означает, что Span<T> сама (а не память, на которую она указывает) может находиться только в стеке. Это в отличие от "обычных" структур C#, которые могут находиться в стеке или в куче (когда они встроены в классы / ссылочные типы). Наиболее очевидные практические последствия состоят в том, что вы не можете иметь Span<T> поле в классе, вы не можете боксировать Span<T>и вы не можете поместить их в массивы.

Общий

Span определяется как ссылочная структура а Memory определяется как структура,

Что это значит?

  • Ссылочные структуры не могут храниться в куче, компилятор не позволит вам это сделать, поэтому следующее не будет разрешено:

    • Использование Span в качестве поля в классе
    • Использование Span в асинхронном методе (асинхронные методы расширяются в полноценный конечный автомат)
    • и многое другое, вот полный список вещей, которые нельзя сделать со ссылочными структурами.
  • stackalloc не будет работать с памятью (потому что нет гарантии, что он не будет храниться в куче), но будет работать с Span

            // this is legit
    Span<byte> data = stackalloc byte[256]; // legit
    
    // compile time error: Conversion of a stackalloc expression of type 'byte' to type 'Memory<byte>' is not possible.
    Memory<byte> data = stackalloc byte[256];
    

Что это означает?

Это означает, что в некоторых сценариях различные микрооптимизации невозможны с самим Span , и поэтому вместо него следует использовать память .

Пример:

Вот пример метода Split без выделения строки , который работает со структурой ReadOnlyMemory , реализовать это на Span было бы очень сложно из-за того, что Span является ссылочной структурой и не может быть помещен в массив или IEnumerable:

(Реализация взята из C# в краткой книге )

      IEnumerable<ReadOnlyMemory<char>> Split(ReadOnlyMemory<char> input)
{
    int wordStart = 0;
    for (int i = 0; i <= input.Length; i++)
    {
        if (i == input.Length || char.IsWhiteSpace(input.Span[i]))
        {
            yield return input.Slice(wordStart, i);
            wordStart = i + 1;
        }
    }
}

А вот результаты очень простого теста с помощью библиотеки тестов dotnet по сравнению с обычным методом Split на .NET SDK=6.0.403.

      |                Method |     StringUnderTest |              Mean |             Error |            StdDev |      Gen0 |      Gen1 |     Gen2 |  Allocated |
|---------------------- |-------------------- |------------------:|------------------:|------------------:|----------:|----------:|---------:|-----------:|
|          RegularSplit |                meow |         13.194 ns |         0.2891 ns |         0.3656 ns |    0.0051 |         - |        - |       32 B |
| SplitOnReadOnlyMemory |                meow |          8.991 ns |         0.1981 ns |         0.2433 ns |    0.0127 |         - |        - |       80 B |
|          RegularSplit | meow(...)meow [499] |      1,077.807 ns |        21.2291 ns |        34.8801 ns |    0.6409 |    0.0095 |        - |     4024 B |
| SplitOnReadOnlyMemory | meow(...)meow [499] |          9.036 ns |         0.2055 ns |         0.2366 ns |    0.0127 |         - |        - |       80 B |
|          RegularSplit | meo(...)eow [49999] |    121,740.719 ns |     2,221.3079 ns |     2,077.8128 ns |   63.4766 |   18.5547 |        - |   400024 B |
| SplitOnReadOnlyMemory | meo(...)eow [49999] |          9.048 ns |         0.2033 ns |         0.2782 ns |    0.0127 |         - |        - |       80 B |
|          RegularSplit | me(...)ow [4999999] | 67,502,918.403 ns | 1,252,689.2949 ns | 2,092,962.4006 ns | 5625.0000 | 2375.0000 | 750.0000 | 40000642 B |
| SplitOnReadOnlyMemory | me(...)ow [4999999] |          9.160 ns |         0.2057 ns |         0.2286 ns |    0.0127 |         - |        - |       80 B |

Входными данными для этих методов были строки «мяу», повторяющиеся 1, 100, 10_000 и 1_000_000 раз, моя настройка теста не была идеальной, но она показывает разницу.

Memory<T> можно рассматривать как небезопасную, но более универсальную версию Span<T>, Доступ к Memory<T> Объект потерпит неудачу, если он указывает на освобожденный массив.

Другие вопросы по тегам