函数内的局部变量*实际上*何时被分配

2023-12-01

只是好奇这个。以下是同一功能的两个代码片段:

void MyFunc1()
{
    int i = 10;
    object obj = null;

    if(something) return;
}

而另外一个是……

void MyFunc1()
{
    if(something) return;

    int i = 10;
    object obj = null;
}

现在,第二个的好处是在以下情况下不分配变量:某物是真的?或者局部堆栈变量(在当前范围内)总是在调用函数时立即分配,并且将 return 语句移至顶部没有效果?

dotnetperls.com 文章的链接 says “当您在 C# 程序中调用方法时,运行时会分配一个单独的内存区域来存储所有局部变量槽。即使您不访问函数调用中的变量,该内存也会在堆栈上分配。”

UPDATED
以下是这两个函数的 IL 代码的比较。 Func2 指的是第二个片段。看起来这两种情况下的变量都是在开始时分配的,尽管在 Func2() 的情况下它们是稍后初始化的。所以我想没有任何好处。

ILCode in ILDisassembler


彼得·杜尼霍的回答是正确的。我想提请大家注意你问题中更根本的问题:

第二个是否具有在以下情况下不分配变量的好处:something是真的?

为什么那应该是一个benefit?您的假设是为局部变量分配空间有一个cost,不这样做有一个benefit并且这种好处在某种程度上是值得获得的。分析局部变量的实际成本是非常非常困难的;有条件地避免分配有明显好处的假设是没有根据的。

要解决您的具体问题:

本地堆栈变量(在当前范围内)总是在函数被调用时立即分配,并且将 return 语句移至顶部没有效果?

我无法轻易回答这么复杂的问题。让我们将其分解为更简单的问题:

变量是存储位置。与局部变量关联的存储位置的生命周期是多少?

“普通”局部变量(以及 lambda 的形式参数、方法等)的存储位置具有较短且可预测的生命周期。他们都没有活着before方法进去了,都没活after该方法正常或异常终止。 C# 语言规范明确规定允许局部变量的生存期shorter在运行时比您想象的要多,如果这样做不会导致单线程程序发生明显的变化。

“不寻常”局部变量的存储位置(lambda 的外部变量、迭代器块中的局部变量、异步方法中的局部变量等)的生命周期很难在编译时或运行时分析,因此移动到垃圾收集堆,它使用 GC 策略来确定变量的生命周期。没有要求此类变量ever被清理干净;它们的存储寿命可以延长任意地取决于 C# 编译器或运行时的突发奇想。

未使用的本地可以完全优化掉吗?

是的。如果 C# 编译器或运行时可以确定从程序中完全删除本地在单线程程序中没有明显的影响,那么它可能会随心所欲地这样做。本质上,这将其寿命缩短为零。

“普通”当地人的存储位置是如何分配的?

这是一个实现细节,但通常有两种技术。要么在堆栈上保留空间,要么注册本地空间。

运行时如何确定本地是否已注册或放入堆栈?

这是抖动优化器的实现细节。有很多因素,例如:

  • 本地地址是否可能被占用;寄存器没有地址
  • 本地是否作为参数传递给另一个方法
  • local是否是当前方法的参数
  • 所有涉及的方法的调用约定是什么
  • 本地的大小
  • 还有很多很多的因素

假设我们只考虑放入堆栈的普通局部变量。当输入方法时是否会分配所有此类局部变量的存储位置?

同样,这是一个实现细节,但通常答案是肯定的。

那么有条件使用的“堆栈本地”不会有条件地从堆栈中分配吗?相反,它的堆栈位置将始终被分配。

通常情况下,是的。

该决定固有的性能权衡是什么?

假设我们有两个局部变量 A 和 B,其中一个有条件使用,另一个无条件使用。哪个更快:

  • 将两个单元添加到当前堆栈指针
  • 将两个新的堆栈槽初始化为零

or

  • 向当前堆栈指针添加一个单元
  • 将新的堆栈槽初始化为零
  • 如果满足条件,则向当前堆栈指针添加 1 个单元,并将新堆栈槽初始化为零

请记住,“添加一个”和“添加两个”具有相同的成本。

这个方案是not如果变量 B 未使用,并且具有twice成本如果is用过的。那不是胜利。

但空间呢?有条件方案使用一个或两个单位的堆栈空间,但无条件方案无论如何都使用两个单位。

正确的。堆栈空间很便宜。或者,更准确地说,每个线程获得的百万字节堆栈空间是贵得离谱,并且该费用已支付up front,当您分配线程时。大多数程序从不使用接近一百万字节的堆栈空间;试图优化该空间的利用就像花一个小时决定是花 5.01 美元买一杯拿铁咖啡,还是在银行存款 100 万美元时花 5.02 美元买一杯拿铁咖啡;这不值得。

假设 100% 的基于堆栈的局部变量是有条件分配的。抖动是否可以将堆栈指针的加法放在条件代码之后?

理论上是的。我不知道抖动是否真的进行了这种优化——这种优化实际上节省了不到十亿分之一秒。请记住,运行抖动来做出节省十亿分之一秒决定的任何代码都是花费远远超过十亿分之一秒的代码。再说一遍,花几个小时担心一分钱是没有意义的。时间就是金钱。

当然,您节省的十亿分之一秒将成为共同路径,这有多现实?大多数方法调用做一点事, not 立即返回.

另外,请记住,堆栈指针必须为所有临时值槽未注册的,无论这些插槽是否已注册names或不。判断方法是否返回的条件有多少种场景itself没有触及堆栈的子表达式吗?因为that's您实际提出的条件得到了优化。这似乎是一组极其微小的场景,在其中你获得的好处也极其微小。如果我正在编写一个优化器,那么当我可以优化更容易实现的目标场景时,我会花费百分之零的宝贵时间来解决这个问题。

假设有two每个局部变量都有条件分配在不同条件下。除了可能进行两次堆栈指针移动(而不是一次或零次移动)之外,条件分配方案是否还会产生额外成本?

是的。在简单的方案中,您将堆栈指针移动两个槽并说“堆栈指针是 A,堆栈指针 + 1 是 B”,您现在有一个贯穿整个方法一致的方法来表征变量 A 和 B。如果您有条件地移动堆栈指针,那么有时堆栈指针是A,有时是B,有时两者都不是。这使得所有代码变得非常复杂usesA和B。

如果本地人注册了怎么办?

那么这就变成了寄存器调度的问题;我建议您参考有关该主题的大量文献。我远非这方面的专家。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数内的局部变量*实际上*何时被分配 的相关文章

随机推荐