问题 符合条件的C#编译器是否可以优化本地(但未使用)变量(如果它是对象的唯一强引用)?


另请参阅以下相关资源:


换一种说法:

可以由本地引用的对象   变量在回收之前被回收   变量超出范围(例如。   因为变量已分配,但是   然后不再使用),或者就是这样   对象保证不符合条件   垃圾收集直到变量   超出范围?

让我解释:


void Case_1()
{
    var weakRef = new WeakReference(new object());

    GC.Collect();  // <-- doesn't have to be an explicit call; just assume that
                   //     garbage collection would occur at this point.

    if (weakRef.IsAlive) ...
}

在这个代码示例中,我显然必须计划新的可能性 object 被垃圾收集者收回;因此 if 声明。

(注意我正在使用 weakRef 仅用于检查是否为新人 object 还在。)


void Case_2()
{
    var unusedLocalVar = new object();
    var weakRef = new WeakReference(unusedLocalVar);

    GC.Collect();  // <-- doesn't have to be an explicit call; just assume that
                   //     garbage collection would occur at this point.

    Debug.Assert(weakRef.IsAlive);
}

这个代码示例与前一个代码示例的主要变化是新的 object 被局部变量强烈引用(unusedLocalVar)。但是,这个变量在弱参考之后永远不会再次使用(weakRef) 已经被创造了。


题: 是否符合C#编译器允许优化前两行 Case_2 进入那些 Case_1 如果它看到了 unusedLocalVar 只用在一个地方,即作为参数 WeakReference 构造函数?即是否有任何可能的断言 Case_2 可能会失败?


7666
2018-04-30 07:02


起源



答案:


C#编译器的作用并不重要 - 一旦它们在方法体中不再存在,JITter / GC就可以清理它们。看看文档 GC.KeepAlive

还有,这个 PowerPoint演示文稿特别是从幻灯片30开始,有助于解释JIT / GC可以达到的目的。


11
2018-04-30 07:10



另请注意,在调试版本中,变量显式保持活动到调试器要查看的范围的末尾 - 只有在发布版本中,您才会看到此行为。 - Andy Mortimer
@Andy - 有趣的一点。这不重要,但我猜这种行为是由JITter管理的? - Damien_The_Unbeliever
而且为了完整性,这就是设置的原因 unusedLocalVar=null 在 结束 该方法通常是去优化的。 - Henk Holterman
@Damien_The_Unbeliever: 谢谢你 GC.KeepAlive 提示,我认为MSDN文档给出了我的问题的95%答案。 - @Henk Holtermann: 这似乎不如安全 GC.KeepAlive;智能编译器无法识别出这样的问题 null 分配是多余的,也是优化它? - stakx
是的,JIT优化器将删除空分配。垃圾收集器从JIT编译器获取局部变量生命周期的提示。 GC.KeepAlive是这种提示的手动版本,它实际上并不生成任何代码。 - Hans Passant


答案:


C#编译器的作用并不重要 - 一旦它们在方法体中不再存在,JITter / GC就可以清理它们。看看文档 GC.KeepAlive

还有,这个 PowerPoint演示文稿特别是从幻灯片30开始,有助于解释JIT / GC可以达到的目的。


11
2018-04-30 07:10



另请注意,在调试版本中,变量显式保持活动到调试器要查看的范围的末尾 - 只有在发布版本中,您才会看到此行为。 - Andy Mortimer
@Andy - 有趣的一点。这不重要,但我猜这种行为是由JITter管理的? - Damien_The_Unbeliever
而且为了完整性,这就是设置的原因 unusedLocalVar=null 在 结束 该方法通常是去优化的。 - Henk Holterman
@Damien_The_Unbeliever: 谢谢你 GC.KeepAlive 提示,我认为MSDN文档给出了我的问题的95%答案。 - @Henk Holtermann: 这似乎不如安全 GC.KeepAlive;智能编译器无法识别出这样的问题 null 分配是多余的,也是优化它? - stakx
是的,JIT优化器将删除空分配。垃圾收集器从JIT编译器获取局部变量生命周期的提示。 GC.KeepAlive是这种提示的手动版本,它实际上并不生成任何代码。 - Hans Passant


虽然我的问题已得到解答,但我想我会在MSDN博客文章中发布我刚发现的相关信息 “WP7:GC何时将局部变量视为垃圾” 通过 abhinaba

[EC] ECMA规范(ECMA 334第10.9节)[...]陈述

“例如,如果范围内的局部变量是对象的唯一现有引用,但该过程中当前执行点的任何可能的继续执行中从不引用该局部变量,则实现可能(但是不要求将对象视为不再使用。“

这说明了一切。上述文章还说,.NET框架(至少在发布模式下)将执行预测分析并释放此类对象,而.NET Compact Framework则不会(出于性能原因)。


3
2018-01-08 09:43





如果一个C#编译器看到unusedLocalVar仅在一个地方使用,即作为WeakReference构造函数的参数,是否允许将Case_2的前两行优化为Case_1的那两行?

这两个定义是等价的,因此从一个转换到另一个不是“优化”,因为它们都不是更有效。

即,Case_2中的断言是否有可能失败?

是。生产编译器不太可能不必要地保留引用,因此它将被删除,GC不会将其视为全局根并将收集该对象。

请注意,垃圾收集器在变量和范围方面看不到您的程序。这些高级概念早在您的代码到达垃圾收集器时就被编译掉了。 GC只能看到寄存器,线程堆栈和全局变量。


0
2018-01-06 14:40