CLR编译器/ JIT是否进行了逃逸分析?例如,在Java中,似乎循环变量在循环中分配的对象不会转义循环,而是在堆栈而不是堆上分配(参见Escape analysis in Java)。 / p>
为了澄清,在下面的例子中,编译器会优化掉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());
}
答案 0 :(得分:12)
如果你的意思是对象(new Foo(i);
),那么我的理解是不:这永远不会在堆栈上分配;然而,它将在第0代死亡,因此收集非常有效。我不是自称知道CLI的每个黑暗和潮湿的角落,但我不知道在C#中的任何情况会导致在堆栈上分配托管引用类型(类似于stackalloc
并不真正重要,而且非常具体。显然在C ++中你有更多选项,但它不是托管实例。
有趣的是,在MonoTouch / AOT上,可能会立即收集,但这不是主要的CLI VM(并且适用于非常特定的场景)。
至于变量 - 通常会在堆栈上(并在每次循环迭代时重复使用) - 但可能不是。例如,如果这是一个“迭代器块”,那么所有未删除的局部变量实际上都是编译器生成的状态机上的字段。更常见的是,如果变量被“捕获”(进入匿名方法或lambda表达式,两者都形成闭包),那么变量将转换为编译器生成的捕获上下文中的字段,和每循环迭代是独立的(因为foo
在循环内声明)。这意味着堆上的每个分开。
至于i
(循环变量) - 如果 被捕获,它会变得更有趣
这只会在捕获变量时产生影响,但会更改它在捕获上下文中显示的 的语义
答案 1 :(得分:5)
可能在堆栈上分配值类型(并非总是如此),但对于引用类型的实例,情况也是如此。事实上:
特别是,引用类型实例的存储位置始终被视为长寿命,即使它们可证明是短暂的。因此,他们总是在堆上。
(Eric Lippert:The Truth About Value Types)
同样The Stack Is An Implementation Detail也能很好地阅读。
答案 2 :(得分:1)
虽然x86 JIT擅长'内联'值类型,但您的代码段不符合条件,因为ToString
方法将成为盒装对象的虚拟调用。 修改:情况可能并非如此,因为您没有覆盖ToString
。
然而x64 JIT在我的实验中完全没有这样做。
修改强>
如果可能,请在x86和x64上测试代码。