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

A9S*_*9S6 3 .net c# stack memory-management local-variables

只是好奇这个.以下是相同功能的两个代码段:

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

    if(something) return;
}
Run Code Online (Sandbox Code Playgroud)

而另一个是......

void MyFunc1()
{
    if(something) return;

    int i = 10;
    object obj = null;
}
Run Code Online (Sandbox Code Playgroud)

现在第二个有没有在某些事情是真的时分配变量的好处?或者,一旦调用函数并且将return语句移到顶部没有效果,总是会分配本地堆栈变量(在当前作用域中)?

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

更新
下面是这两个函数的IL代码的比较.Func2指的是第二次剪断.似乎两个案例中的变量都是在开头分配的,但是在Func2()的情况下,它们稍后会被初始化.所以我猜这样没有任何好处.

ILDisassembler中的ILCode

Eri*_*ert 23

Peter Duniho的回答是正确的.我想提请你注意你问题中更基本的问题:

当第二个something是真的时,第二个有没有分配变量的好处?

为什么这应该是一个好处?您的假设是为局部变量分配空间会产生成本,不这样做会带来好处,并且这种好处在某种程度上值得获得.分析局部变量的实际成本非常非常困难; 假设有条件地避免分配有明显的好处,这是不合理的.

解决您的具体问题:

调用函数后,总是会分配本地堆栈变量(在当前作用域中),并将return语句移动到顶部无效?

我不能轻易回答这么复杂的问题.让我们把它分解成更简单的问题:

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

"普通"局部变量的存储位置 - 以及lambda,方法等的形式参数 - 具有短暂,可预测的寿命.他们没有生活之前进入的方法,并没有人住后,该方法终止,正常或异常.C#语言规范清楚地指出,如果这样做不会导致对单线程程序的可观察更改,则允许本地变量生命周期在运行时比您想象的更短.

"异常"局部变量的存储位置 - lambda的外部变量,迭代器块中的局部变量,异步方法中的局部变量等等 - 具有在编译时或运行时难以分析的生命周期,因此移动到垃圾收集堆,它使用GC策略来确定变量的生命周期.有没有这样的变量需要不断进行清理; 它们的存储寿命可以延长任意在C#编译器或运行时的兴致.

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

是.如果C#编译器或运行时可以确定从程序中删除local完全在单线程程序中没有可观察到的影响,那么它可能会随心所欲地这样做.基本上这是将其寿命减少到零.

如何分配"普通"本地人的存储位置?

这是一个实现细节,但通常有两种技术.堆栈上保留任何空间,或者注册本地.

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

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

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

假设我们只考虑放在堆栈上的普通本地人.是否在输入方法时分配所有此类本地的存储位置?

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

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

通常,是的.

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

假设我们有两个本地,A和B,一个是有条件的,另一个是无条件使用的.哪个更快:

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

要么

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

请记住,"添加一个"和"添加两个"具有相同的成本.

该方案是便宜,如果变量B是未使用的,并且具有两倍的成本,如果它使用.那不是胜利.

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

正确.堆栈空间很便宜.或者,更准确地说,是百万字节的堆栈空间,你每个线程得到的是出奇的昂贵,以及费用支付前面,当你分配线程.大多数程序从不使用接近一百万字节的堆栈空间; 试图优化这个空间的使用就像花一个小时决定是否支付5.01美元的拿铁咖啡与5.02美元,当你在银行有一百万美元; 这不值得.

假设有条件地分配100%基于堆栈的本地.抖动是否可以在条件代码之后添加到堆栈指针?

从理论上讲,是的.抖动是否真正实现了这种优化 - 一种节省不到十亿分之一秒的优化 - 我不知道.请记住,抖动运行的任何代码都可以决定节省十亿分之一秒的代码,这些代码需要的时间远远超过十亿分之一秒.再说一次,花几个小时担心便士是没有意义的; 时间就是金钱.

当然,你节省的第十亿分之一将成为共同的道路是多么现实?大多数方法调用做一些事情,而不是立即返回.

此外,请记住,无论这些插槽是否具有名称,堆栈指针都必须移动所有未注册的临时值插槽.有多少场景确定方法是否返回自身的条件没有接触堆栈的子表达式?因为这是你实际提出的条件得到优化.这看起来像是一组极其微小的场景,在这些场景中你获得的收益微乎其微.如果我正在编写一个优化器,那么我将花费百分之零的宝贵时间来解决这个问题,因为我可以优化的是多汁的低悬的水果场景.

假设有两个本地人在不同条件下有条件地分配.除了可能执行两个堆栈指针移动而不是一个或零之外,条件分配方案是否会产生额外成本?

是.在简单的方案中,您将堆栈指针移动两个插槽并说"堆栈指针为A,堆栈指针+ 1为B",您现在可以使用一致的方法来表征变量A和B.如果您有条件地移动堆栈指针然后有时堆栈指针是A,有时它是B,有时它不是.这使得使用 A和B的所有代码变得非常复杂.

如果当地人被注册怎么办?

那么这就成了寄存器调度的一个问题; 我将向您介绍有关该主题的大量文献.我远非专家.


Pet*_*iho 10

只有这样,才能知道肯定发生这种情况时你的程序,当你运行它,是看JIT编译器发出的代码,当你运行你的程序.我们都不能用权威回答具体问题(好吧,我想有人写过CLR可以,只要他们知道你正在使用哪个版本的CLR,以及可能的其他一些关于配置和你的实际程序代码的细节).

对局部变量堆栈的任何分配都是严格的"实现细节".并且CLS不向我们承诺任何具体实施.

一些本地人本身永远不会堆叠在堆栈上,通常是由于存储在寄存器中,但是运行时使用堆空间是合法的,只要它保留本地变量的正常生命周期语义即可.

另见Eric Lippert的优秀系列The Stack Is An Implementation Detail