Stackalloc 与 C# 中固定大小的缓冲区。有什么不同

Via*_*vus 6 c# performance callstack

就我而言,以下代码将在堆栈上创建一个数组:

unsafe struct Foo 
{ 
   public fixed int bar[10]; 
}
var foo = new Foo();
Run Code Online (Sandbox Code Playgroud)

stackalloc 语句会做同样的事情:

public void unsafe foo(int length)
{
    Span<int> bar = stackalloc int[length];
}
Run Code Online (Sandbox Code Playgroud)

所以我想知道这些方法之间有什么区别。固定大小缓冲区的目的是什么?每个人都在谈论性能提升,但我不明白为什么我需要它们,当我已经可以使用 stackalloc 在堆栈上创建数组时。MSDN 表示固定大小的缓冲区用于与其他平台“互操作”。那么这个“互操作”是什么样的呢?

Fre*_*eft 8

fixed声明仅表示数组内联(固定在结构内部)。这意味着存储在数组中的数据直接存储在您的结构中。在您的示例中,Foo结构体的大小可以存储 10 个整数值。由于结构是值类型,因此它们在堆栈上分配。但是,例如,当将它们存储在引用类型中时,它们也可以复制到堆中。

class Test1
{
    private Foo Foo = new();
}

unsafe struct Foo
{
    public fixed int bar[10];
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将编译并且私有Foo实例将存在于托管堆上。

如果没有固定语句(只是一个“普通”int[]),数组的数据将不会存储在结构本身中,而是存储在堆上。该结构仅拥有对该数组的引用。

使用时stackalloc数据分配在堆栈上,并且不能由 CLR 自动移动到堆中。这意味着堆栈分配的数据保留在堆栈上,编译器将通过不允许这样的代码来强制执行此操作:

unsafe class Test1
{
    // CS8345: Field or auto-implemented property cannot be of type 'Span<int>' unless it is an instance member of a ref struct.

    private Span<int> mySpan;

    public Test1()
    {
        // CS8353: A result of a stackalloc expression of type 'Span<int>' cannot be used in this context because it may be exposed outside of the containing method
        mySpan = stackalloc int[10];
    }
}
Run Code Online (Sandbox Code Playgroud)

stackalloc因此,当您绝对希望确保分配的数据不会逃逸到堆中时使用,从而导致抓取收集器的压力增加(这是一个性能问题)。fixed另一方面主要用于与本机C/C++库的互操作场景,本机库可能由于某种原因使用内联缓冲区。因此,当从本机世界调用以内联缓冲区的结构作为参数的方法时,您必须能够在 .NET 中重新创建它,否则您将无法轻松使用本机代码(因此该语句fixed存在)。使用的另一个原因fixed是内联结构中的数据,这可以在 CPU 访问它时实现更好的缓存,因为它可以一次性读取所有数据,而Foo无需取消引用引用并跳转内存来访问某些数据。数组存储在其他地方。