[英]Practical use of `stackalloc` keyword
有沒有人在stackalloc
編程時實際使用過 stackalloc? 我知道 is 做了什么,但它唯一一次出現在我的代碼中是偶然的,因為 Intellisense 在我開始輸入static
時提示它,例如。
雖然它與stackalloc
的使用場景無關,但實際上我在我的應用程序中做了相當多的遺留互操作,所以我時不時地使用unsafe
的代碼。 但是盡管如此,我通常會找到完全避免unsafe
的方法。
由於 .Net 中單個線程的堆棧大小為 ~1Mb(如果我錯了請糾正我),我對使用stackalloc
更加保留。
是否有一些實際案例可以說:“這對我來說是正確的數據量和處理 go 不安全並使用stackalloc
”?
使用stackalloc
的唯一原因是性能(用於計算或互操作)。 通過使用stackalloc
而不是堆分配的數組,可以減少GC的壓力(GC需要運行的時間更少),不需要固定數組,分配的速度比堆數組快,它會自動釋放方法出口(僅當GC運行時才釋放分配給堆的數組)。 同樣,通過使用stackalloc
代替本機分配器(如malloc或.Net等效項),您還可以在范圍退出時獲得速度和自動釋放。
在性能方面,如果使用stackalloc
,則由於數據的局部性,會大大增加CPU上緩存命中的機會。
我已經使用stackalloc為[近]實時DSP工作分配緩沖區。 在一個非常特殊的情況下,性能需要盡可能保持一致。 請注意,一致性和總體吞吐量之間存在差異-在這種情況下,我並不擔心堆分配太慢,而只是在程序中此時沒有確定的垃圾回收。 我不會在99%的情況下使用它。
stackalloc
僅與不安全的代碼有關。 對於托管代碼,您無法決定在何處分配數據。 默認情況下,值類型是在堆棧上分配的(除非它們是引用類型的一部分,在這種情況下,它們是在堆上分配的)。 引用類型在堆上分配。
普通香草.NET應用程序的默認堆棧大小為1 MB,但是您可以在PE標頭中更改此大小。 如果要顯式啟動線程,則還可以通過構造函數重載來設置其他大小。 對於ASP.NET應用程序,默認堆棧大小僅為256K,如果要在兩種環境之間切換,則應牢記這一點。
范圍的Stackalloc初始化。 在早期版本的C#中,stackalloc的結果只能存儲在指針局部變量中。 從C#7.2開始,現在可以將stackalloc用作表達式的一部分,並且可以將其作為目標范圍,而無需使用unsafe關鍵字即可完成此操作。 因此,與其寫作
Span<byte> bytes;
unsafe
{
byte* tmp = stackalloc byte[length];
bytes = new Span<byte>(tmp, length);
}
您可以簡單地編寫:
Span<byte> bytes = stackalloc byte[length];
這在需要一些暫存空間來執行操作但又希望避免為較小的內存分配堆內存的情況下非常有用
Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>
遲到的答案,但我相信仍然有幫助。
我來到這個問題,我仍然好奇看到性能差異所以我創建了以下基准(使用 BenchmarkDotNet NuGet 包):
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[RankColumn]
public class Benchmark1
{
//private MemoryStream ms = new MemoryStream();
static void FakeRead(byte[] buffer, int start, int length)
{
for (int i = start; i < length; i++)
buffer[i] = (byte) (i % 250);
}
static void FakeRead(Span<byte> buffer)
{
for (int i = 0; i < buffer.Length; i++)
buffer[i] = (byte) (i % 250);
}
[Benchmark]
public void AllocatingOnHeap()
{
var buffer = new byte[1024];
FakeRead(buffer, 0, buffer.Length);
}
[Benchmark]
public void ConvertingToSpan()
{
var buffer = new Span<byte>(new byte[1024]);
FakeRead(buffer);
}
[Benchmark]
public void UsingStackAlloc()
{
Span<byte> buffer = stackalloc byte[1024];
FakeRead(buffer);
}
}
這就是結果
| Method | Mean | Error | StdDev | Rank | Gen 0 | Allocated |
|----------------- |---------:|---------:|---------:|-----:|-------:|----------:|
| UsingStackAlloc | 704.9 ns | 13.81 ns | 12.91 ns | 1 | - | - |
| ConvertingToSpan | 755.8 ns | 5.77 ns | 5.40 ns | 2 | 0.0124 | 1,048 B |
| AllocatingOnHeap | 839.3 ns | 4.52 ns | 4.23 ns | 3 | 0.0124 | 1,048 B |
這個基准表明使用stackalloc
是最快的解決方案,而且它不使用分配! 如果您對如何使用 NuGet Package BenchmarkDotNet 感到好奇,請觀看此視頻。
這個問題有很好的答案,但我只想指出
Stackalloc也可以用來調用本地API
許多本機函數要求調用者分配一個緩沖區以獲取返回結果。 例如, CfGetPlaceholderInfo在功能cfapi.h
具有以下特征。
HRESULT CfGetPlaceholderInfo(
HANDLE FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID InfoBuffer,
DWORD InfoBufferLength,
PDWORD ReturnedLength);
為了通過互操作在C#中調用它,
[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);
您可以使用stackalloc。
byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.