【问题标题】:Why is stack size in C# exactly 1 MB?为什么 C# 中的堆栈大小正好是 1 MB?
【发布时间】:2015-04-23 18:40:55
【问题描述】:

今天的 PC 拥有大量物理 RAM,但 C# 的堆栈大小对于 32 位进程只有 1 MB,对于 64 位进程只有 4 MB (Stack capacity in C#)。

为什么 CLR 中的堆栈大小仍然如此有限?

为什么它正好是 1 MB (4 MB)(而不是 2 MB 或 512 KB)?为什么决定使用这些金额?

我对该决定背后的考虑和原因感兴趣。

【问题讨论】:

  • 64 位进程的默认堆栈大小为 4MB,32 位进程为 1MB。您可以通过更改其 PE 标头中的值来修改主线程堆栈大小。您还可以使用Thread 构造函数的右重载来指定堆栈大小。但是,这引出了一个问题,为什么需要更大的堆栈?
  • 谢谢,已编辑。 :) 问题不在于如何使用更大的堆栈大小,而是为什么决定堆栈大小为 1 MB (4 MB)
  • 因为默认情况下每个线程都会得到这个堆栈大小,而且大多数线程不需要那么多。我刚刚启动了我的电脑,系统当前运行了 1200 个线程。现在做数学;)
  • @LucasTrzesniewski 不仅如此,它还必须在记忆中具有传染性。请注意堆栈的大小越大,您的进程可以在其虚拟地址空间中创建的线程就越少。
  • 不确定“确切”为 1 MB:在我的 Windows 8.1 上,.NET Core 3.1 控制台应用程序的默认堆栈大小为 1572864 字节(使用 GetCurrentThreadStackLimits Win32 API 检索)。我能够在没有 StackOverflowException 的情况下 stackalloc 大约 1500000 字节。

标签: c# stack clr stack-size


【解决方案1】:

您正在寻找做出该选择的人。 David Cutler 和他的团队选择 1 MB 作为默认堆栈大小。与 .NET 或 C# 无关,这是在他们创建 Windows NT 时确定的。当程序的 EXE 标头或 CreateThread() winapi 调用未明确指定堆栈大小时,它会选择一兆字节。这是正常的方式,几乎所有程序员都让操作系统来选择大小。

这种选择可能早于 Windows NT 的设计,历史对此太模糊了。如果卡特勒能写一本关于它的书就好了,但他从来都不是作家。他对计算机的工作方式具有非凡的影响力。他的第一个操作系统设计是 RSX-11M,一个用于 DEC 计算机(Digital Equipment Corporation)的 16 位操作系统。它极大地影响了 Gary Kildall 的 CP/M,这是第一个体面的 8 位微处理器操作系统。这严重影响了 MS-DOS。

他的下一个设计是 VMS,这是一个支持虚拟内存的 32 位处理器操作系统。非常成功。他的下一个在公司开始解体时被 DEC 取消,无法与廉价的 PC 硬件竞争。提示微软,他们给了他一个他无法拒绝的提议。他的许多同事也加入了。他们在 VMS v2 上工作,更广为人知的是 Windows NT。 DEC对此感到不安,钱转手来解决它。我不知道 VMS 是否已经选择了 1 兆字节,我只对 RSX-11 足够了解。不太可能。

足够的历史。一兆字节是一个很多,一个真正的线程很少消耗超过几千字节。所以一兆字节实际上是相当浪费的。然而,在按需分页的虚拟内存操作系统上,这是您可以承受的那种浪费,那兆字节只是 虚拟内存。只是给处理器编号,每 4096 个字节一个。在你真正解决它之前,你永远不会真正使用物理内存,即机器中的 RAM。

在 .NET 程序中它是多余的,因为最初选择 1 兆字节的大小是为了容纳本机程序。这往往会创建大型堆栈帧,也会在堆栈上存储字符串和缓冲区(数组)。作为恶意软件攻击媒介而臭名昭著,缓冲区溢出可以用数据操纵程序。不是 .NET 程序的工作方式,在 GC 堆上分配字符串和数组并检查索引。使用 C# 在堆栈上分配空间的唯一方法是使用 unsafe stackalloc 关键字。

.NET 中堆栈的唯一重要用途是抖动。它使用线程堆栈将 MSIL 即时编译为机器代码。我从未见过或检查过它需要多少空间,这取决于代码的性质以及是否启用了优化器,但几十千字节是一个粗略的猜测。这就是这个网站得名的方式,.NET 程序中的堆栈溢出是非常致命的。没有足够的空间(小于 3 KB)来仍然可靠地 JIT 任何试图捕获异常的代码。 Kaboom 到桌面是唯一的选择。

最后但并非最不重要的一点是,.NET 程序在堆栈上做了一些非常低效的事情。 CLR 将提交线程的堆栈。这是一个昂贵的词,意味着它不仅保留堆栈的大小,还确保在操作系统的页面文件中保留空间,以便在必要时始终可以换出堆栈。未能提交是一个致命错误,并无条件终止程序。这只发生在运行太多进程的 RAM 非常少的机器上,这样的机器在程序开始死亡之前就会变成糖蜜。 15 多年前可能存在的问题,而不是今天。将程序调整为像 F1 赛车一样工作的程序员在他们的 .config 文件中使用 <disableCommitThreadStack> 元素。

首先,卡特勒并没有停止设计操作系统。这张照片是他在 Azure 上工作时拍摄的。


更新,我注意到 .NET 不再提交堆栈。不完全确定发生这种情况的时间或原因,自从我检查以来已经太久了。我猜这种设计更改发生在 .NET 4.5 附近。相当明智的改变。

【讨论】:

  • w.r.t.对您的评论The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword. - 是局部变量,例如int 在未存储在堆栈中的方法中声明?我认为他们是。
  • 好的。现在我知道堆栈框架不是存储函数局部变量的唯一选择。它可以存储在堆栈框架中,尽管如您的一个要点中所建议的那样。非常有启发性的汉斯。我不能说足够的感谢你写了这么有见地的帖子。老实说,堆栈对于一般编程来说是一个很大的抽象,只是为了避免不必要的复杂性。
  • 非常详细的描述@Hans。我只是想知道线程的maxStackSize 的最小可能值是多少?我在 [MSDN] (msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx) 上找不到它。根据您的 cmets,似乎堆栈使用量绝对是最低的,我可以使用最小值来容纳最大可能的线程。谢谢。
  • @KFL:你可以通过尝试轻松回答你的问题!
  • 如果默认行为是不再提交栈,那么这个markdown文件需要编辑github.com/dotnet/docs/blob/master/docs/framework/…
【解决方案2】:

默认保留堆栈大小由链接器指定,开发人员可以通过在链接时更改 PE 值或通过为 CreateThread WinAPI 函数指定 dwStackSize 参数来覆盖单个线程。

如果您创建的线程的初始堆栈大小大于或等于默认堆栈大小,则它会向上舍入到最接近的 1 MB 倍数。

为什么 32 位进程的值等于 1 MB 而 64 位进程的值等于 4 MB?我认为您应该询问设计 Windows 的开发人员,或者等到他们中的某个人回答您的问题。

可能 Mark Russinovich 知道这一点,你可以contact他。也许您可以在他的第六版之前的 Windows Internals 书籍中找到此信息,其中描述的有关堆栈的信息较少,而不是他的 article。或者也许 Raymond Chen 知道原因,因为他写了一些关于 Windows 内部结构及其历史的有趣的东西。他也可以回答您的问题,但您应该向Suggestion Box 提出建议。

但此时我将尝试解释 Microsoft 使用 MSDN、Mark 和 Raymond 的博客选择这些值的一些可能原因。

默认值具有这些值可能是因为早期 PC 速度较慢,并且在堆栈上分配内存比在堆中分配内存要快得多。而且由于堆栈分配便宜得多,因此使用它们,但它需要更大的堆栈大小。

因此,该值是大多数应用程序的最佳保留堆栈大小。这是最佳的,因为允许进行大量嵌套调用并在堆栈上分配内存以将结构传递给调用函数。同时它允许创建很多线程。

如今,这些值主要用于向后兼容,因为作为参数传递给 WinAPI 函数的结构仍分配在堆栈上。但是,如果您不使用堆栈分配,那么线程的堆栈使用量将大大低于默认的 1 MB,并且正如 Hans Passant 所提到的那样是浪费的。并且为了防止这种情况,如果应用程序的 PE 标头中未指定其他页面,则操作系统仅提交堆栈的第一页 (4 KB)。其他页面按需分配。

一些应用程序会覆盖保留的地址空间并最初致力于优化内存使用。例如,IIS 本机进程线程的最大堆栈大小为 256 KB (KB932909)。而这种默认值的减少是微软的recommended

最好选择尽可能小的堆栈大小,并提交线程或光纤可靠运行所需的堆栈。为堆栈保留的每个页面都不能用于任何其他目的。

来源:

  1. Thread Stack Size (Microsoft Docs)
  2. Pushing the Limits of Windows: Processes and Threads (Mark Russinovich)
  3. By default, the maximum stack size of a thread that is created in a native IIS process is 256 KB (KB932909)

【讨论】:

  • 如果我想要更大的堆栈大小,我可以设置它 (atalasoft.com/cs/blogs/rickm/archive/2008/04/22/…)。 我想知道这个决定背后的考虑和原因。
  • 好的。现在我明白了 :) 默认堆栈大小应该是最佳的(请参阅@Lucas Trzesniewski 评论),并且应该四舍五入到最接近的分配粒度倍数。如果指定的堆栈大小大于默认堆栈大小,则四舍五入到最接近的 1MB 倍数。所以微软选择这个大小作为所有用户模式应用程序的默认堆栈大小。而且没有其他原因。
  • 任何来源?有什么文件吗? :)
  • @Yoh 有趣的链接。您应该将其总结到您的答案中。
猜你喜欢
  • 2015-12-09
  • 2016-02-15
  • 2022-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多