C# 'stackalloc'关键字的实际使用

C# 'stackalloc'关键字的实际使用,c#,keyword,stackalloc,C#,Keyword,Stackalloc,有没有人在用C#编程时实际使用过stackalloc?我知道is的作用,但它在我的代码中出现的唯一时间是偶然的,因为Intellisense会在我开始键入static时提示它 虽然它与stackalloc的使用场景无关,但实际上我在我的应用程序中进行了大量的遗留互操作,因此我可以时不时地使用不安全的代码。但无论如何,我通常会找到方法来完全避免不安全 由于.Net中单个线程的堆栈大小约为1Mb(如果我错了,请纠正我),因此我更不愿意使用stackalloc 在一些实际案例中,有人会说:“这正是我不

有没有人在用C#编程时实际使用过
stackalloc
?我知道is的作用,但它在我的代码中出现的唯一时间是偶然的,因为Intellisense会在我开始键入
static
时提示它

虽然它与
stackalloc
的使用场景无关,但实际上我在我的应用程序中进行了大量的遗留互操作,因此我可以时不时地使用
不安全的
代码。但无论如何,我通常会找到方法来完全避免不安全

由于.Net中单个线程的堆栈大小约为1Mb(如果我错了,请纠正我),因此我更不愿意使用
stackalloc


在一些实际案例中,有人会说:“这正是我不安全地使用
stackalloc
”的正确数据量和处理方式吗?

stackalloc
仅与不安全代码相关。对于托管代码,您无法决定在何处分配数据。值类型根据默认值在堆栈上分配(除非它们是引用类型的一部分,在这种情况下,它们是在堆上分配的)。引用类型是在堆上分配的


普通.NET应用程序的默认堆栈大小为1MB,但您可以在PE头中更改此大小。如果显式启动线程,还可以通过构造函数重载设置不同的大小。对于ASP.NET应用程序,默认堆栈大小仅为256K,如果要在两个环境之间切换,请记住这一点

使用stackalloc的唯一原因是性能(用于计算或互操作)。通过使用
stackalloc
而不是堆分配的数组,您可以创建更少的GC压力(GC需要运行的更少),您不需要固定数组,分配速度比堆数组快,并且在方法退出时自动释放(堆分配的数组仅在GC运行时解除分配)。另外,通过使用
stackalloc
而不是本机分配器(如malloc或.Net等效程序),您还可以在范围退出时获得速度和自动解除分配


就性能而言,如果使用stackalloc,由于数据的局部性,CPU上的缓存命中率会大大增加。

我使用stackalloc为[近]实时DSP工作分配缓冲区。这是一个非常特殊的情况,性能需要尽可能一致。请注意,一致性和总体吞吐量之间存在差异——在本例中,我不关心堆分配太慢,只关心程序中此时垃圾收集的不确定性。我不会在99%的情况下使用它。

Stackalloc跨度初始化。在以前版本的C#中,stackalloc的结果只能存储到指针局部变量中。从C#7.2开始,stackalloc现在可以作为表达式的一部分使用,并且可以以跨度为目标,这可以在不使用不安全关键字的情况下完成。因此,与其写作

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}
Span字节;
不安全的
{
字节*tmp=stackalloc字节[长度];
字节=新跨度(tmp,长度);
}
你可以简单地写:

Span<byte> bytes = stackalloc byte[length];
Span bytes=stackalloc byte[length];
这在需要一些暂存空间来执行操作的情况下也非常有用,但要避免将堆内存分配给相对较小的大小

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Span bytes=length这个问题有一些很好的答案,但我只想指出一点

Stackalloc还可用于调用本机API

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>
许多本机函数要求调用方分配缓冲区以获得返回结果。例如,
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);

数据的局部性,很好!当您想要分配多个结构或阵列时,托管内存很少能够实现这一点。谢谢托管对象的堆分配通常比非托管对象快,因为没有空闲列表可遍历;CLR只是增加堆指针。至于局部性,由于堆压缩,顺序分配更有可能最终在长时间运行的托管进程中实现同一个位置。“分配比堆数组更快”这是为什么?只是地点?无论哪种方式,它都只是一个指针凹凸,不是吗?@MaxBarraclough,因为在应用程序生命周期内,您将GC成本添加到堆分配中。总分配成本=分配+解除分配,在本例中为指针凹凸+GC堆,与指针凹凸+指针减量堆栈。是否可以从Visual Studio更改默认堆栈大小?@configurator:据我所知,没有。刚刚注意到,
System.Numbers
使用它非常感谢您的提示。看来C的每一个新版本都离C++有点近,这实际上是一件好事,如可以看到的,<代码> SUP在.NETFramework 4.7.2中是不可用的,甚至在4.8中也不可用。因此,新的语言功能目前仍然使用有限。@Frederic:显然,.NETFramework4.8是最好的。下一个版本的.NET Core将被调用(不再使用“Core”,并跳过版本4以避免与框架混淆),因此.NET的未来是.NET Core,现在很明显,.NET framework应用程序将不会获得此更新。我也相信这个想法是为了在将来删除.NET标准(因为从现在开始只有.NET)。