.NET CLR VM中的转义分析

Sim*_*onC 15 c# clr optimization jit escape-analysis

是否有CLR编译器/ JIT执行的转义分析?例如,在Java中,似乎循环变量是在循环中分配的不会转义循环的对象在堆栈而不是堆上分配(请参阅Java中的Escape分析).

为了澄清,在下面的示例中,编译器会优化掉堆分配,foo因为它永远不会从循环中逃脱.

class Foo 
{ 
   int number;
   Foo(int number) { this.number = number; }
   public override string ToString() { return number.ToString(); }
}

for (int i = 0; i < 10000000; i++)
{
   Foo foo = new Foo(i);
   Console.WriteLine(foo.ToString());
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 12

如果你的意思是object(new Foo(i);),那么我的理解是不:这永远不会在堆栈上分配; 然而,它将在第0代死亡,因此收集非常有效.我不是自称知道CLI的每个黑暗和潮湿的角落,但我不知道C#中的任何情况会导致在堆栈上分配托管引用类型(事情就像stackalloc没有真正算在内,并且非常具体).显然在C++中你有更多选项,但它不是托管实例.

有趣的是,在MonoTouch/AOT上可能会立即收集它,但这不是主要的CLI VM(并且适用于非常特定的场景).

至于变量 -这通常是在栈上(并重新用于每次循环迭代) -但它可能不是.例如,如果这是一个"迭代器块",那么所有未删除的局部变量实际上都是编译器生成的状态机上的字段.更常见的是,如果变量被"捕获"(进入匿名方法或lambda表达式,两者都形成闭包),那么变量将转换为编译器生成的捕获上下文中的字段,并且每循环迭代是独立的(因为foo在循环内声明了).这意味着每个堆都是独立的.

至于i(循环变量) -如果被捕获,它会变得更加有趣

  • 在C#1.2中,捕获不存在,但是根据规范,循环变量在技术上是每次迭代
  • 在C#2.0到4.0中,循环变量是共享的(导致臭名昭着的捕获/ foreach常见问题)
  • 在C#5.0及更高版本中,循环变量再次进行迭代

这不仅使当变量被捕获的差异,但变化的准确语义如何它体现在捕捉上下文


Teu*_*ndo 5

可能在堆栈上分配值类型(并非总是),但对于引用类型的实例则不同.事实上:

特别是,引用类型实例的存储位置总是被视为长寿命,即使它们可证明是短暂的.因此他们总是在堆上.

(Eric Lippert:关于价值类型的真相)

此外堆栈是一个实现细节,使一个良好的阅读.