.NET CLR VM 中的转义分析
CLR 编译器/JIT 是否执行任何逃逸分析?例如,在Java中,似乎循环变量在循环中分配的不会逃脱循环的对象被分配在堆栈上而不是堆上(请参阅Java 中的转义分析)。
为了澄清,在下面的示例中,编译器是否会优化 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());
}
Is there any escape analysis performed by the CLR compiler/JIT? For example, in Java it appears that a loop variable an object allocated in a loop that doesn't escape the loop gets allocated on the stack rather than the heap (see Escape analysis in Java).
To clarify, in the example below, would the compiler optimise away the heap allocation of foo
as it never escapes the loop.
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());
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您指的是对象(
new Foo(i);
),那么我的理解是不:这永远不会在堆栈上分配;然而,它会在零代中死亡,因此收集起来非常高效。我并不自称了解 CLI 的每一个阴暗角落,但我不知道 C# 中的任何场景会导致在堆栈上分配托管引用类型(例如stackalloc
并不真正重要,并且非常具体)。显然,在 C++ 中您还有更多选择,但它不是托管实例。有趣的是,在 MonoTouch/AOT 上,它可能会立即被收集,但这不是主要的 CLI VM(并且适用于非常具体的场景)。
至于变量 - 通常将位于堆栈上(并在每次循环迭代中重复使用) - 但它可能不会。例如,如果这是一个“迭代器块”,则所有未删除的局部变量实际上都是编译器生成的状态机上的字段。更常见的是,如果变量被“捕获”(进入匿名方法或 lambda 表达式,两者都形成闭包),则该变量将转换为编译器生成的捕获上下文上的字段,和 每个循环迭代都是单独的(因为
foo
在循环内声明)。这意味着每个在堆上都是独立的。至于
i
(循环变量) - 如果 that 被捕获,它会变得更有趣,这仅在捕获变量时产生影响,但会改变它在捕获上下文中具体表现的语义
If you mean the object (
new Foo(i);
), then my understanding is that no: this is never allocated on the stack; however, it will die in generation zero, so will be very efficient to collect. I don't profess to know every dark and dank corner of the CLI, but I am not aware of any scenario in C# that would lead to a managed reference-type being allocated on the stack (things likestackalloc
don't really count, and are highly specific). Obviously in C++ you have a few more options, but then it isn't a managed instance.Interestingly, on MonoTouch/AOT it might be collected immediately, but that is not the main CLI VM (and is for a very specific scenario).
As for the variable - that will usually be on the stack (and re-used for each loop iteration) - but it might not be. For example, if this is an "iterator block", then all the non-removed local-variables are actually fields on the compiler-generated state-machine. More commonly, if the variable is "captured" (into an anonymous method or lambda expression, both of which form closures), then the variable is transformed into a field on the compiler-generated capture-context, and is separate per loop iteration (since
foo
is declared inside the loop). This means that each is separate on the heap.As for
i
(the loop variable) - if that gets captured, it gets even more interestingthis only makes a difference when the variable is captured, but changes the semantics of exactly how it manifests on the capture-context
值类型可能会在堆栈上分配(并非总是如此),但对于引用类型的实例则不然。实际上:
(埃里克·利珀特:关于值类型的真相)
还有堆栈是一个实现细节值得一读。
A value type might be allocated on the stack (not always), but the same is not true for instances of reference types. In fact:
(Eric Lippert: The Truth About Value Types)
Also The Stack Is An Implementation Detail makes a good reading.
虽然 x86 JIT 擅长“内联”值类型,但您的代码片段不符合条件,因为
ToString
方法将是对装箱对象的虚拟调用。 编辑:情况可能并非如此,因为您没有覆盖ToString
。然而,根据我的实验,x64 JIT 根本没有这样做。
编辑:
如果可能,请在 x86 和 x64 上测试您的代码。
While the x86 JIT is good at 'inlining' valuetypes, your snippet will not qualify as the
ToString
method will be a virtual call on a boxed object. Edit: This may not be the case, as you are not overridingToString
.The x64 JIT however does not do this at all from my experiments.
Edit:
If possible, test your code on both x86 and x64.