为什么C#中的堆栈大小正好是1 MB?

时间:2015-02-22 10:36:39

标签: c# stack clr stack-size

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

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

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

我对该决定背后的注意事项和原因感兴趣

2 个答案:

答案 0 :(得分:168)

enter image description here

你正在看那个做出这个选择的人。 David Cutler和他的团队选择了一兆字节作为默认堆栈大小。与.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是否已经选择了一兆字节是我不知道的,我只知道RSX-11。这不太可能。

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

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

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

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

Fwiw,Cutler并没有停止设计操作系统。那张照片是他在Azure上工作时制作的。


更新,我注意到.NET不再提交堆栈。不完全确定何时或为何发生这种情况,自从我检查以来已经太久了。我猜这个设计更改发生在.NET 4.5的某个地方。非常明智的改变。

答案 1 :(得分:3)

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

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

为什么32位进程的值等于1 MB而64位的值为4 MB?我想你应该问设计Windows的开发人员,或者等到有人回答你的问题。

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

但此时我会尝试解释微软使用MSDN,Mark和Raymond博客选择这些价值的一些可能原因。

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

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

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

某些应用程序覆盖保留的地址空间,并且最初致力于优化内存使用。例如,IIS本机进程的线程的最大堆栈大小为256 KB(KB932909)。 Microsoft的默认值减少为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)
相关问题