我们可以编写自己的简单性能改进来减少不必要的垃圾
你重新发现了一个特殊情况公共子表达式消除-- 优化识别何时两个或多个表达式具有完全相同的值,计算该值一次,并将其存储在变量中以供重复使用。
在继续之前,我提醒您,所有所谓的“优化”实际上都是在以一件事换另一件事。您建议的优化会用每次调用时生成的少量收集压力来换取少量内存泄漏。静态字段中的缓存值将成为第 2 代堆的永久成员。这值得吗?您需要通过实际测量来回答这个问题。
对于这个非常简单的情况,Roslyn 有什么理由不能进行类似的优化吗?
没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化.
特别是,优化会导致之前值相等但引用不相等的两个委托变得引用相等。这可能是可以接受的。
在实践中,实现优化需要在设计、实现、测试和维护执行优化的代码方面付出大量的努力。 C# 不实现常见的子表达式消除优化。这种优化的性价比很差。很少有人编写可以从优化中受益的代码,而且优化很小,而且正如您发现的那样,如果您关心的话,很容易“手动”进行优化。
我注意到 C# 确实对 lambda 做了类似的缓存。它不会进行常见的子表达式消除,但只会生成一次某些 lambda 并缓存结果:
void M() { Action x = () => {}; ... }
生成就像你写的:
static Action anon = null;
void M()
{
if (anon == null) anon = () => {};
Action x = anon;
...
如果答案是否定的,那么当方法不是静态而是实例成员时怎么办?
没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化.
我注意到,在这种情况下,当然需要进行优化来推断实例何时相同。如果不这样做,就无法维持程序行为不得改变的不变性。
同样,在实践中,C# 不执行公共子表达式消除。
当捕获了封闭变量时该怎么办?
被什么捕获了?您刚才讨论的是方法组到委托的转换,显然现在我们正在讨论 lambda 转换为委托。
C# 规范明确指出,编译器可以选择对相同的 lambda 执行公共子表达式消除,也可以不执行,只要它认为合适。
没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化。由于规范明确指出允许这种优化,因此根据定义它是可以接受的。
同样,在实践中,C# 不执行公共子表达式消除。
也许您在这里注意到了一种趋势。问题的答案“是否允许这样那样的优化?”几乎总是“是的,如果它不会对程序的行为产生不可接受的变化”。但问题的答案是“C# 在实践中是否实现了这样那样的优化?”通常是没有。
如果您想了解编译器执行的优化的一些背景知识,我在 2009 年描述过它们.
大多数情况下,Roslyn 在这些优化方面做得更好。例如,Roslyn 在将临时值和局部变量具体化为临时变量而不是持久变量方面做得更好。我完全重写了可为空算术优化器;我的八部分系列文章描述了这里的情况。还有更多的改进。但我们从未考虑过进行 CSE。