【问题标题】:Why can't the compiler optimize closure variable by inlining?为什么编译器不能通过内联优化闭包变量?
【发布时间】:2019-02-04 19:48:29
【问题描述】:

我有一个像这样的Main 方法:

static void Main(string[] args)
{
     var b = new byte[1024 * 1024];

     Func<double> f = () =>
     {
         new Random().NextBytes(b);
         return b.Cast<int>().Average();
     };

     var avg = f();
     Console.WriteLine(avg);
}

由于我在这里访问一个局部变量b,编译器创建一个类来捕获该变量,b 成为该类的字段。然后b 的生命周期与编译器生成的类的生命周期一样长,它会导致内存泄漏。即使b 超出范围(可能不在这种情况下,但想象这是在另一个方法内部而不是Main),字节数组也不会被释放。

我想知道的是,既然在声明 Func 后我没有在任何地方访问或修改 b,为什么编译器不能内联该局部变量而不费心创建一个类?像这样:

Func<double> f = () =>
{
    var b = new byte[1024 * 1024];
    new Random().NextBytes(b);
    return b.Cast<int>().Average();
};

我在 Debug 和 Release 模式下编译了这段代码,DisplayClass 是在这两种模式下生成的:

这只是没有作为优化实现还是我缺少什么?

【问题讨论】:

  • 提示:使用本地函数而不是委托,编译器将捕获类型生成为结构而不是类:sharplab.io/…

标签: c# closures compiler-optimization cil


【解决方案1】:

这只是没有作为优化实现还是我缺少什么?

对于您给出的特定示例,您可能不想进行该代码转换,因为它会改变程序的语义。如果new 抛出异常,则在原始程序中它应该在执行委托之前这样做,并且在您的转换中,副作用被延迟。这是否是应该保留的重要财产是有争议的。 (这样做也会给调试器带来问题;调试器已经必须假装闭包类的元素是包含方法体的局部变量,而这种优化可能会使它进一步复杂化。)

但是,更普遍的观点是密切相关的。如果您知道封闭变量仅用于其值,则可以进行许多优化。

当我在编译器团队时 -- 我于 2012 年离开 -- Neal Gafter 和我考虑实施此类优化,以及一些更复杂的优化,旨在减少昂贵对象的生命周期延长过长的可能性意外地。

旁白:更复杂的场景中最简单的是:我们将两个 lambda 转换为委托;一个存储在一个短暂的变量中,并在一个包含对昂贵对象的引用的局部变量上关闭;一个存储在一个长期存在的变量中,并在一个引用廉价对象的本地变量上关闭。昂贵的对象与长期存在的变量一样长,即使它没有被使用。更一般地,多个闭包可以构建为基于封闭关系的分区;当时我们只基于嵌套对闭包进行分区;同一嵌套级别的闭包是一个闭包。给定的场景很少见,并且有明显的解决方法,但如果它根本不发生就好了。

我们没有这样做,因为在我们实施 Roslyn 期间有更重要的优化和功能,我们不想给已经很长的计划增加风险。

我们可以自信地执行此类优化,因为在 C# 中很容易知道局部变量何时被别名化,因此您可以确定在创建闭包后它是否被写入。

我不知道这些优化是否同时实现了;可能不会。

我也不知道编译器是否对 C# 7 本地函数进行了此类优化,但我怀疑答案是“是”。看看如果你尝试一个本地函数会发生什么!

【讨论】:

  • re 局部函数:它变成了一个结构体:sharplab.io/…
  • @MarcGravell:我就是这么想的;感谢您的确认。
  • 我已经尝试过本地函数,正如@MarcGravell 指出的那样,编译器会创建一个结构。但我想仍然有机会按照我建议的方式进行优化。如果我没有访问局部函数之外的局部变量,它可以被内联。
  • 感谢@EricLippert 的回答,我试图理解这句话:“多个闭包可以构造为基于封闭关系的分区;”但无法在我的脑海中想象它,你能详细说明一下吗?一个简短的代码示例可能对我和未来的读者有所帮助:)
  • @SelmanGenç:我的意思是:考虑我的例子:lambda 1 在局部 x 上封闭,lamba 2 在 y 上封闭,我们为 x 和 y 做一个封闭。我们可以为 x 做一个闭包,为 y 做一个闭包。现在假设我们有 lambda 1 在 x 和 y 上封闭,lambda 2 在 x、y 和 z 上封闭,lambda 3 在 z 上封闭。正确的闭包数量是多少,可以最大限度地减少创建的闭包数量(因为我们不想要 GC 压力)并确保没有 lambda 使变量保持太长时间?如果你想做对的话,那就是要解决的分区问题。
猜你喜欢
  • 1970-01-01
  • 2011-04-17
  • 1970-01-01
  • 2018-06-16
  • 1970-01-01
  • 2014-10-16
  • 2015-09-15
  • 2019-08-11
  • 2018-11-14
相关资源
最近更新 更多