最近在看一些 open source 專案的原始碼時,看到某個地方用到了 stackalloc
這關鍵字讓我一點一熟悉看一下文件這不是以前要開 unsafe 的東西
結果一查,才發現這玩意兒原來早在以前就有,只是因為後來有了 Span<T>,
現在可以安全地在受控環境裡使用堆疊記憶體,完全不需要 unsafe 了。
簡單講,stackalloc 是用來在 stack 上配置一段暫時的記憶體區域。
一般我們用 new byte[256],這會在 heap 裡分配,交給 GC 管理。
但 stackalloc 是直接開一塊暫存空間,不用 GC,也不用釋放,方法結束後自動消失。
因為 為他很快,但是也有一些限制我們先看他多快
public static int GetBytesByByte(string text)
{
byte[] buffer = new byte[256];
return Encoding.UTF8.GetBytes(text, 0, text.Length, buffer, 0);
}
public static int GetBytesByStackalloc(string text)
{
Span buffer = stackalloc byte[256];
return Encoding.UTF8.GetBytes(text, buffer);
}
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
string text = Guid.NewGuid().ToString() + Guid.NewGuid().ToString();
int iterations = 10_000_000; // 執行次數
// --- 用 new byte[] ---
var sw1 = Stopwatch.StartNew();
for (int round = 1; round <= 10; round++)
{
Parallel.For(0, iterations, i =>
{
GetBytesByByte(text);
});
}
Console.WriteLine($"new byte[] 花費時間:{sw1.ElapsedMilliseconds} ms");
sw1.Stop();
// --- 用 stackalloc ---
var sw2 = Stopwatch.StartNew();
for (int round = 1; round <= 10; round++)
{
Parallel.For(0, iterations, i =>
{
GetBytesByStackalloc(text);
});
}
Console.WriteLine($"stackalloc 花費時間:{sw2.ElapsedMilliseconds} ms");
sw2.Stop();
}
結果:
new byte[] 花費時間:3675 ms
stackalloc 花費時間:955 ms
new byte[] 花費時間:3599 ms
stackalloc 花費時間:951 ms
new byte[] 花費時間:3951 ms
stackalloc 花費時間:875 ms
new byte[] 花費時間:3662 ms
stackalloc 花費時間:905 ms
說說一下他的限制
限制1: 每個執行緒的堆疊空間大概只有 1MB,所以在設計 function 的時候要注意
限制2: 記憶體只在當前方法有效,方法一結束就釋放,不能回傳這個 Span、也不能存在外部物件中。
限制3: 無法在 async / iterator 裡使用
如果你這樣寫 compile 就會 error
async Task FooAsync()
{
Span buffer = stackalloc byte[256]; //compile error
await Task.Delay(1);
}
result:
大概筆記到這邊,畢竟是要追求極致效能才會使用到的東西,實際上常常寫起來都會忘記可以使用