20

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

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

请注意,这个问题是 .NET 特定的

1 问题已从 SO 中删除。

4

7 回答 7

18

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

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

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

于 2009-01-25T07:40:06.613 回答
5

编辑: 我的原始答案包含过度简化的“结构在堆栈上分配”并且混淆了堆栈与堆和值与引用的关系,因为它们在 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)甚至可以确定在堆栈上分配对象是否安全,即使它们不是值类型。

于 2009-01-25T03:42:03.520 回答
4

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

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

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

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

但是,由于堆是垃圾收集而堆栈不是,显然在某些情况下您会看到一些差异,但考虑到您在 .NET 中实际上没有选择的事实,这几乎不相关。

于 2009-01-25T13:58:28.760 回答
3

我认为最简单的原因是,如果它在堆中,那么一旦不再需要垃圾收集就需要处理该变量。在堆栈上时,该变量会被任何使用它的东西所解除,例如实例化它的方法。

于 2009-01-25T03:20:23.690 回答
3

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

于 2011-08-11T13:45:50.823 回答
1

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

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

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

于 2018-06-20T09:43:19.440 回答
1

我在堆栈和堆上玩了很多不同的基准测试,我总结如下:

类似的性能

  • 小应用程序(堆上没有太多对象)
  • 大小 < 1KB 的对象

Sligther 更好的堆栈性能(快 1 倍 - 5 倍)

  • 大小为 1Kb 到 100KB 的对象

更好的性能(速度提高 100 倍甚至更多)

  • 大量对象
  • 大内存压力 - 每秒大量分配和完整内存
  • 大对象 10KB - 3MB(我想是 x64 系统)
  • Xbox(慢速垃圾收集器)

最好的方法是数组池。它和堆栈一样快,但你没有堆栈那样的限制。

使用堆栈的另一个含义是通过设计它是线程安全的。

x64 Windows 上堆栈的默认内存限制为 4MB。因此,分配不超过 3MB 的空间是安全的。

于 2019-03-18T19:57:00.443 回答