.Net Span<T> API and Memory<T> API Study
June 27, 2020
Microsoft 在 2018 年一月的 MSDN Magazine 初步釋出了 Span API 的相關資訊,
這組 API 目前在 System.Memory 的 MemoryExtensions 下
主要可以產生以下四種類別
- Span<T>
- Memory<T>
- ReadOnlySpan<T>
- 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
使用時間: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
使用時間: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
使用時間: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
使用時間: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 實作
使用時間: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 實作
使用時間: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> 的一些差異
- Span<T> 只能用在同步方法, Memory<T> 可以用在非同步方法。
- Memory<T> 可以轉成 Span<T> ,Span<T>不能轉成 Memory<T>。
- Span<T> 在 Stack 上,Memory<T> 在 Heap 上。
- Span<T> 不能通過ValueTuple方式回傳,Memory<T> 可以。
- Span<T> 不能成為類別的成員,Memory<T>可以。
- Span<T> 不能手動釋放,Memory<T> 可以透過 Pin() 方法通知 runtime 由開發者手動回收。
- 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: