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超出范围(可能不是在这种情况下,但想象这是在另一个方法而不是Main),字节数组将不会被释放.
我想知道的是,因为我在声明Func后没有访问或修改b,为什么编译器不能内联该局部变量而不打扰创建类?像这样:
Func<double> f = () => { var b = new byte[1024 * 1024]; new Random().NextBytes(b); return b.Cast<int>().Average(); };
我在Debug和Release模式下编译了这段代码,DisplayClass在两者中生成:
这是不是作为优化实现还是我缺少什么?
解决方法
Is this just not implemented as an optimization or is there anything I am missing?
对于您给出的具体示例,您可能不希望进行代码转换,因为它会更改程序的语义.如果new抛出异常,在原始程序中它应该在执行委托之前这样做,并且在转换中,副作用被推迟.这是否是一个应该保留的重要财产是值得商榷的. (这样做也会给调试器带来问题;调试器必须假装闭包类的元素是包含方法体的本地元素,并且这种优化可能会使其进一步复杂化.)
然而,更普遍的一点是密切相关.如果您知道封闭变量仅用于其值,则可以执行许多优化.
当我参加编译团队时 – 我在2012年离职 – 我和Neal Gafter考虑实施这样的优化,以及一些更复杂的优化,旨在降低昂贵对象的生命周期长时间延长的可能性.
旁白:最复杂的场景中最简单的是:我们将两个lambdas转换为委托;一个存储在一个短期变量中,并在一个包含对昂贵对象的引用的本地上关闭;一个存储在一个长期存在的变量中,并在一个引用廉价对象的本地上关闭.即使没有使用,昂贵的物体也会存在长寿命变量.更一般地,可以基于封闭关系将多个闭包构造为分区;当时我们只基于嵌套来分区闭包;在同一嵌套层面的封闭是一个封闭.给定的场景很少见,并且有明显的解决方法,但如果它根本没有发生会很好.
我们没有这样做,因为在我们实施Roslyn期间有更重要的优化和功能,我们不想为已经很长的时间表增加风险.
我们可以自信地执行这样的优化,因为在C#中很容易知道本地何时被别名化,因此您可以确定是否在创建闭包之后写入它.
我不知道这些优化是否已经同时实施;可能不是.