C#编译器会优化此代码吗?

时间:2014-02-18 20:51:24

标签: c# .net optimization compiler-construction

我经常遇到这种情况。乍一看,我认为,“这是糟糕的编码;我正在执行一个方法两次并且必然得到相同的结果。“但是在考虑之后,我不得不怀疑编译器是否像我一样聪明并且可以得出相同的结论。

var newList = oldList.Select(x => new Thing {
    FullName = String.Format("{0} {1}", x.FirstName, x.LastName),
    OtherThingId = x.GetOtherThing() != null : x.GetOtherThing().Id : 0 // Might call x.GetOtherThing() twice?
});

编译器的行为是否依赖于GetOtherThing方法的内容?说它看起来像这样(有点类似于我现在的真实代码):

public OtherThing GetOtherThing() {
    if (this.Category == null) return null;
    return this.Category.OtherThings.FirstOrDefault(t => t.Text == this.Text);
}

这样,除非对这些对象来自的任何商店进行非常糟糕的异步更改,否则如果连续运行两次,肯定会返回相同的内容。但是,如果它看起来像这样(为了论证而荒谬的例子):

public OtherThing GetOtherThing() {
    return new OtherThing {
        Id = new Random().Next(100)
    };
}

连续两次运行将导致创建两个不同的对象,很可能具有不同的ID。编译器在这些情况下会做什么?它是否像我在第一次上市时看到的那样低效?

自己做一些工作

我运行了与第一个代码清单非常相似的东西,并在GetOtherThing实例方法中添加了一个断点。断点被击中一次。所以,看起来结果确实是缓存的。在第二种情况下会发生什么,该方法可能每次返回不同的东西?编译器会不正确地优化?我发现结果有什么警告吗?

修改

该结论无效。请参阅@ usr的答案下的评论。

2 个答案:

答案 0 :(得分:12)

这里有两个编译器需要考虑:将C#转换为IL的C#编译器和将IL转换为机器代码的IL编译器 - 称为抖动,因为它恰好发生在时间上。

Microsoft C#编译器肯定没有这样的优化。方法调用是作为方法调用生成的,故事结束。

允许抖动执行您描述的优化,前提是无法检测。例如,假设你有:

y = M() != 0 ? M() : N()

static int M() { return 1; }

允许允许将此程序转换为:

y = 1 != 0 ? 1 : N()

或者就此而言

y = 1;

抖动是否这样做是一个实现细节;如果你愿意的话,你将不得不向专家询问是否真的会执行这种优化。

同样,如果你有

static int m;
static int M() { return m; }

然后抖动可以将其优化为

y = m != 0 ? m : N()

甚至进入:

int q = m;
y = q != 0 ? q : N();

因为只允许该字段不是易失性的,所以允许抖动连续转换两个字段读取而不插入单个字段读取。无论它是否这样做都是一个实现细节;问一个抖动的开发人员。

但是,在后一个例子中,抖动不能忽略第二个调用,因为它有副作用。

  

我运行了与第一个代码清单非常相似的东西,并在GetOtherThing实例方法中放置了一个断点。断点被击中一次。

这是非常不可能的。在调试时几乎所有优化都会被关闭,这样就可以更容易地进行调试。作为Sherlock Holmes never said,当你消除不可能的时候,最可能的解释是原来的海报是错误的。

答案 1 :(得分:9)

如果您无法区分,编译器只能应用优化。在您的“随机”示例中,您可以清楚地区分出来。它不能以这种方式“优化”。它会违反C#规范。事实上,该规范并未谈及优化问题。它只是说你应该观察程序做什么。在这种情况下,它指定应绘制两​​个随机数。

在第一个示例中,可以应用此优化。它在实践中永远不会发生。以下是一些令人困难的事情:

  • 查询操作的数据可以通过您的虚函数调用来更改,或者您的lambda(t => t.Text == this.Text)可以更改列表。非常阴险。
  • 可以通过其他线程进行更改。我不确定.NET内存模型对此有何看法。
  • 可以通过反思来改变。
  • 必须证明计算总是返回相同的值。你会如何证明这一点?您需要分析可能运行的所有代码。包括虚拟调用和依赖于数据的控制流。

所有这些都必须适用于非内联方法和跨程序集。

C#编译器无法执行此操作,因为它无法查看mscorlib。修补程序版本可能随时更改mscorlib。

JIT是一个糟糕的JIT(唉),它针对编译速度进行了优化(唉)。它没有这样做。如果您对目前的JIT是否会进行一些高级优化存在疑问,可以肯定它不会。