堆与堆栈分配的影响(.NET)

从关于堆和堆栈的SO回答 1中 ,它提出了一个问题:为什么知道变量的分配位置很重要?

在另一个答案,有人指出堆栈更快。 这是唯一的含义吗? 有人可以给出一个代码示例,其中简单的分配位置更改可以解决问题(例如,性能)?

请注意,此问题是特定于.NET的

1问题从SO中删除。

只要你知道语义是什么,堆栈与堆的唯一结果就是确保你不会溢出堆栈,并意识到垃圾收集堆有相关的成本。

例如,JIT 可能会注意到新创建的对象从未在当前方法之外使用(引用永远不能在其他地方转义)并在堆栈上分配它。 目前它不会这样做,但这是合法的事情。

同样,C#编译器可以决定在堆上分配所有局部变量 – 堆栈只包含对MyMethodLocalVariables实例的引用,并且所有变量访问都将通过它实现。 (实际上,委托或迭代器块捕获的变量已经具有这种行为。)

你的问题出现了,而Eric Lippert正在深入审查C# – 我有一节解释C#1中的内容,他认为开发人员不应该关心 。

编辑: 我的原始答案包含过度简化“结构在堆栈上分配”和混淆堆栈与vs-heap和值与引用的关系,因为它们在C#中耦合。

对象是否存在于堆栈中是一个不太重要的实现细节。 乔恩已经很好地解释了这一点。 在使用类和结构体之间进行选择时,更重要的是要认识到引用类型与值类型的工作方式不同。 以下面的简单类为例:

 public class Foo { public int X = 0; } 

现在考虑以下代码:

 Foo foo = new Foo(); Foo foo2 = foo; foo2.X = 1; 

在此示例中,foo和foo2是对同一对象的引用。 在foo2上设置X也会影响foo1。 如果我们将Foo类更改为结构,则不再是这种情况 。 这是因为结构不能通过引用访问。 分配foo2实际上会复制。

将东西放入堆栈的原因之一是垃圾收集器不必清理它。 你通常不应该担心这些事情; 只是使用课程! 现代垃圾收集器做得很好。 一些现代虚拟机(如java 1.6)甚至可以确定在堆栈上分配对象是否安全,即使它们不是值类型。

我认为最简单的原因是,如果它在堆中,垃圾收集需要在不再需要时处理变量。 在堆栈上时,变量将被使用它的任何内容解除,例如实例化它的方法。

在.NET中几乎没有什么可讨论的,因为不是类型的用户决定在哪里分配实例。

始终在堆上分配引用类型。 默认情况下,在堆栈上分配值类型。 例外情况是值类型是引用类型的一部分,在这种情况下,它与引用类型一起分配在堆上。 即类型的设计者代表用户做出这个决定。

在诸如C或C ++之类的语言中,用户可以决定数据的分配位置,对于某些特殊情况,与从堆分配相比,从堆栈分配可能要快得多。

这与C / C ++如何处理堆分配有关。 事实上,在.NET中堆分配相当快(除非它触发垃圾收集),所以即使你可以决定在哪里分配,我的猜测是差异不会很大。

但是,由于堆是垃圾收集而堆栈不是,显然你会在某些情况下看到一些差异,但鉴于你在.NET中没有真正的选择,它几乎不相关。

在我看来,当你真正开始考虑应用程序的性能时,了解堆栈和堆之间的差异以及如何在其上分配内容可能非常有用。 以下问题使得理解差异变得至关重要:您认为.NET访问的速度更快,效率更高? – 堆叠或堆。 在什么情况下.NET可以放置堆的值类型?

与流行的看法相反,在.NET进程中堆栈和堆栈之间没有那么大的区别。 堆栈和堆只不过是虚拟内存中的地址范围,并且与为托管堆保留的地址范围相比,保留给特定线程堆栈的地址范围没有固有的优势。 访问堆上的内存位置既不比访问堆栈上的内存位置更快也更慢。 在某些情况下,有几个注意事项可能支持对堆栈位置的内存访问总体上比对堆位置的内存访问更快的声明。 其中:

  1. 在堆栈上,时间分配局部性(在时间上靠近在一起的分配)意味着空间局部性(在空间中靠近在一起的存储)。 反过来,当时间分配局部性意味着时间访问局部性(一起分配的对象被一起访问)时,顺序堆栈存储倾向于相对于CPU高速缓存和操作系统分页系统执行得更好。
  2. 由于引用类型开销,堆栈上的内存密度往往高于堆上的内存密度。 较高的内存密度通常会带来更好的性能,例如,因为更多的对象适合CPU缓存。
  3. 线程堆栈往往相当小 – Windows上的默认最大堆栈大小为1MB,大多数线程实际上只使用少量堆栈页面。 在现代系统中,所有应用程序线程的堆栈都可以放入CPU缓存中,从而使典型的堆栈对象访问速度极快。 (另一方面,整个堆很少适合CPU缓存。)

话虽如此,你不应该把所有的分配都移到堆栈! Windows上的线程堆栈是有限的,通过应用不明智的递归和大堆栈分配很容易耗尽堆栈。