kevin wang's blog

.Net Span<T> API and Memory<T> API Study

June 27, 2020

Microsoft 在 2018 年一月的 MSDN Magazine 初步釋出了 Span API 的相關資訊,
這組 API 目前在 System.Memory 的 MemoryExtensions 下

主要可以產生以下四種類別

  1. Span<T>
  2. Memory<T>
  3. ReadOnlySpan<T>
  4. ReadOnlyMomory<T>

Span<T> 與 Memory<T> 是一種對記憶體操作的API,表示一段連續的記憶體。讓開發者可以更安全的操作記憶體。

Span<T> 與 Memory<T> 主要是透過以下 API 來存取記憶體的一個區段

public Span<T> Slice(int start);
public ReadOnlySpan<T> Slice(int start);
public Span<T> Slice(int start, int length);
public ReadOnlySpan<T> Slice(int start, int length);

public Memory<T> Slice(int start);
public ReadOnlyMemory<T> Slice(int start);
public Memory<T> Slice(int start, int length);
public ReadOnlyMemory<T> Slice(int start, int length);

假如是一串字串的話, 用 Span<T> 與 Memory<T> 可以直接指定某段記憶體位置而不用直接多放一段字串到字串池

假如要從 Hello World 切出 or 這個字串的時候

使用 String.SubString 會變成

字串池
---------------------------
|H|e|l|l|o| |W|o|r|l|d|o|r|
---------------------------

而使用 Span 會變成

字串池
-----------------------
|H|e|l|l|o| |W|o|r|l|d|
-----------------------
              |   |
              span1

以下是對 Span API 做的一些對比測試


常見的字串分割操作

String.SubString

使用 String SubString 做一百萬次 SubString

Github repo

使用時間:21ms

[System.Runtime]
    % Time in GC since last GC (%)                         4
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      1
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                        24
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                       288
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                   115,216
    LOH Size (B)                                      19,640
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            7
    ThreadPool Completed Work Item Count / 1 sec           1
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      23

使用ReadOnlySpan<char> 做一百萬次 Slice

Github repo

使用時間:22ms

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      0
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            8
    ThreadPool Completed Work Item Count / 1 sec           0
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      20

使用 ReadOnlyMemory<char> 做一百萬次 Slice

Github repo

使用時間:33ms

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      0
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            8
    ThreadPool Completed Work Item Count / 1 sec           0
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      20

String.Split

使用 String 做一百萬次 Split

Github repo

使用時間:177ms

[System.Runtime]
    % Time in GC since last GC (%)                         1
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      2
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                        24
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                    11,400
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                   115,200
    LOH Size (B)                                      19,640
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            7
    ThreadPool Completed Work Item Count / 1 sec           1
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      23

使用 ReadOnlySpan<char> 做一百萬次 Split 實作

Github repo

使用時間:222ms

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      0
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            8
    ThreadPool Completed Work Item Count / 1 sec           0
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      19

使用 ReadOnlyMemory<char> 做一百萬次 Split 實作

Github repo

使用時間:224ms

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      0
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                0
    Number of Assemblies Loaded                            8
    ThreadPool Completed Work Item Count / 1 sec           0
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      20

Span<T> 與 Memory<T> 的一些差異

  1. Span<T> 只能用在同步方法, Memory<T> 可以用在非同步方法。
  2. Memory<T> 可以轉成 Span<T> ,Span<T>不能轉成 Memory<T>。
  3. Span<T> 在 Stack 上,Memory<T> 在 Heap 上。
  4. Span<T> 不能通過ValueTuple方式回傳,Memory<T> 可以。
  5. Span<T> 不能成為類別的成員,Memory<T>可以。
  6. Span<T> 不能手動釋放,Memory<T> 可以透過 Pin() 方法通知 runtime 由開發者手動回收。
  7. Span<T> 不能作為泛型類型參數,Memory<T>可以。

*造成Span<T> 與 Memory<T> 的一些差異主要都在於 Span<T> 是放在 Stack 上所產生出的限制,

一些可用的原生API

目前一些原生 API 已經支援以 Span<T> 與 Memory<T> 當成參數,比方說

String

public static string Concat (ReadOnlySpan<char> str0, ReadOnlySpan<char> str1);

Utf8Formatter

public static bool TryFormat (int value, Span<byte> destination, out int bytesWritten, System.Buffers.StandardFormat format = default);

Utf8Parser

public static bool TryParse (ReadOnlySpan<byte> source, out int value, out int bytesConsumed, char standardFormat = '\0');

ref:

  1. MSDN - C# - 全面了解 Span:探索新的 .NET 重要支援
  2. MSDN - Memory 與 Span 使用指導方針