实际使用`stackalloc`关键字


134

stackallocC#编程时有没有人真正使用过?我知道它是做什么的,但是它唯一一次出现在我的代码中是偶然的,因为例如Intellisense在我开始键入时就建议这样static做。

尽管它与的使用情况无关stackalloc,但实际上我在我的应用程序中做了相当多的旧互操作,因此我时不时地可以诉诸使用unsafe代码。但是,尽管如此,我通常会找到unsafe完全避免的方法。

而且由于.Net中单个线程的堆栈大小约为1Mb(如果我写错了,请纠正我),因此我对使用保留了更多stackalloc

在某些实际情况下,有人会说:“对于我来说,使用不安全的数据和处理量恰到好处stackalloc”?


5
刚刚注意到它System.Numbers使用了很多referencesource.microsoft.com/#mscorlib/system/…–
Slai

Answers:


150

使用的唯一原因stackalloc是性能(用于计算或互操作)。通过使用stackalloc而不是堆分配的数组,可以减少GC压力(GC需要运行的时间更少),不需要固定数组,分配的速度比堆数组要快,它会在方法上自动释放退出(仅在GC运行时才释放分配给堆的数组)。此外,通过使用stackalloc代替本地分配器(如malloc或.Net等效项),还可以在范围退出时获得速度和自动释放。

在性能方面,如果您使用stackalloc它,则由于数据的局部性,会大大增加CPU上缓存命中的机会。


26
数据的局部性,好点!当您要分配多个结构或数组时,托管内存几乎无法实现。谢谢!
Groo

22
托管对象的堆分配通常比非托管对象更快,因为没有空闲列表可以遍历。CLR只是增加堆指针。至于局部性,由于堆压缩,对于长时间运行的托管进程,顺序分配更有可能最终并置。
乳木果

1
“分配比堆数组要快”这是为什么?只是地方?无论哪种方式,它都只是一个指针碰撞,不是吗?
Max Barraclough

2
@MaxBarraclough因为您在整个应用程序生存期内将GC成本添加到堆分配中。总分配成本=分配+释放,在这种情况下为指针碰撞+ GC堆,vs指针碰撞+指针减量堆栈
Pop Catalin

35

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


25

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

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


是否可以从Visual Studio更改默认堆栈大小?
配置器2010年

@configurator:据我所知。
Brian Rasmussen 2010年

17

范围的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>

来源: C#-有关跨度的一切:探索新的.NET主流


4
谢谢你的提示。似乎每个新版本的C#都离C ++有点近,这实际上是一件好事,恕我直言。
Groo

1
可以在这里这里看到,Span可惜在.NET Framework 4.7.2中甚至在4.8中都不可用。因此,新语言功能目前仍受限制。
弗雷德里克(Frederic),

2

这个问题有很好的答案,但我只想指出

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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.