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

时间:2015-03-26 05:06:26

标签: c# .net memory-management stack local-variables

对此感到好奇。以下是相同功能的两个代码片段:

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

    if(something) return;
}

另一个是......

void MyFunc1()
{
    if(something) return;

    int i = 10;
    object obj = null;
}

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

A link to dotnetperls.com article“当你在C#程序中调用一个方法时,运行时分配一个单独的内存区域来存储所有的局部变量槽。即使你不这样做,这个内存也会在堆栈中分配访问函数调用中的变量。“

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

ILCode in ILDisassembler

2 个答案:

答案 0 :(得分:23)

彼得·邓尼奥的回答是正确的。我想提请你注意你问题中更基本的问题:

  

something为真时,第二个是否具有NOT分配变量的好处?

为什么要成为的好处?您的假设是为局部变量分配空间具有成本,不这样做有成本并且这种好处在某种程度上值得获得。分析局部变量的实际成本非常非常困难;有条件避免分配有明显好处的假设是不合理的。

解决您的具体问题:

  

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

我无法轻易回答这么复杂的问题。让我们把它分解成更简单的问题:

  

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

"普通"的存储位置局部变量 - 以及lambdas的形式参数,方法等 - 具有短暂的,可预测的寿命。在输入方法之前,他们都没有,并且在方法终止后,无论是正常还是异常,都没有。 C#语言规范清楚地指出,如果这样做不会导致对单线程程序的可观察更改,那么在运行时允许局部变量生命周期更短

"异常"的存储位置局部变量 - lambdas的外部变量,迭代器块中的局部变量,异步方法中的局部变量等等 - 具有在编译时或运行时难以分析的生命周期,因此被转移到垃圾收集heap,它使用GC策略来确定变量的生命周期。不要求清除这些变量 ;他们的存储生命周期可以随意扩展任意 C#编译器或运行时。

  

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

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

  

"普通"的存储位置如何?当地人分配了什么?

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

  

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

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

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

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

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

  

所以"堆栈本地"有条件地使用的是否会有条件地从堆栈中分配出来?相反,它的堆栈位置将始终被分配。

通常,是的。

  

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

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

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

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

请记住"添加一个"并且"添加两个"有相同的成本。

如果变量B未使用,则此方案更便宜;如果 ,则两次成本。这不是一场胜利。

  

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

正确。堆栈空间很便宜。或者,更准确地说,每个线程获得的百万字节堆栈空间非常昂贵,并且当您分配线程时,该费用是预先支付。大多数程序从不使用接近一百万字节的堆栈空间;试图优化这个空间的使用就像花一个小时决定是否支付5.01美元的拿铁咖啡与5.02美元,当你在银行有一百万美元;它不值得。

  

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

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

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

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

  

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

是。在简单的方案中,您将堆栈指针移动两个插槽并说“#34;堆栈指针是A”,堆栈指针+ 1是B&#34;,您现在可以使用一致的方法来表征变量A如果你有条件地移动堆栈指针,那么有时堆栈指针是A,有时它是B,有时它不是。这使使用 A和B的所有代码变得非常复杂。

  

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

然后这成为寄存器调度的问题;我将向您介绍有关该主题的大量文献。我远非专家。

答案 1 :(得分:10)

确定何时发生 for your program 当你运行时,唯一的方法是查看JIT编译器发出的代码你运行你的程序。我们都不能用权威回答具体问题(好吧,我想有人写过CLR可以,只要他们知道你正在使用哪个版本的CLR,以及可能的其他一些关于配置和你的实际程序代码的细节)。

局部变量堆栈上的任何分配都是严格的“实现细节”。而CLS并没有向我们承诺任何具体的实施。

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

另见Eric Lippert的精彩系列The Stack Is An Implementation Detail