为什么编译器不能通过内联优化闭包变量?

时间:2018-08-29 23:22:20

标签: c# closures compiler-optimization cil

我有一个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内),字节数组也不会被释放。

我想知道的是,由于在声明b之后我没有在任何地方访问或修改Func,为什么编译器不能内联该局部变量并且不打扰创建类?像这样:

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

我在Debug和Release模式下编译了此代码,DisplayClass在以下两种方式中生成:

enter image description here

这只是不是作为优化来实现,还是我缺少什么?

1 个答案:

答案 0 :(得分:12)

  

这只是不是作为优化来实现,还是我缺少什么?

对于您给出的 specific 示例,您可能不希望进行代码转换,因为它会更改程序的语义。如果new引发异常,则在原始程序中应在执行委托之前引发异常,而在您的转换中,将延迟副作用。那是否应该保留的重要财产值得商bat。 (这样做还会给调试器带来问题;调试器已经必须假装闭包类的元素是包含方法主体的局部元素,这种优化可能会使它进一步复杂化。)

但是,更一般的观点是德语。如果您知道封闭变量仅用于其值,则可以进行许多优化。

当我在编译器团队工作时-我于2012年离开-Neal Gafter和我考虑实施此类优化,以及许多旨在降低昂贵对象的寿命被延长的可能性的更复杂的优化意外地。

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

我们之所以没有这样做,是因为在实施罗斯林期间,还有更多重要的优化和功能,并且我们不想在已经很长的时间表中增加风险。

我们可以放心地执行此类优化,因为在C#中,很容易知道何时对本地进行了别名,因此您可以确定在创建闭包后是否曾经将其写入。

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

我也不知道编译器是否对C#7本地函数进行了这种优化,尽管我怀疑答案是“是”。看看尝试使用本地函数会发生什么!