C#编译器不会优化不必要的类型转换。

31

几天前,在stackoverflow上为这个问题写答案时,我对C#编译器感到有点惊讶,因为它没有按照我预期的那样工作。请看以下两段代码:

第一段:

object[] array = new object[1];

for (int i = 0; i < 100000; i++)
{
    ICollection<object> col = (ICollection<object>)array;
    col.Contains(null);
}

第二点:

object[] array = new object[1];

for (int i = 0; i < 100000; i++)
{
    ICollection<object> col = array;
    col.Contains(null);
}
两个代码片段之间唯一的区别在于对ICollection<object>的强制转换。由于object[] 显式地实现了 ICollection<object> 接口,我本以为这两个代码片段会编译成相同的IL代码,从而达到完全相同的效果。然而,在对它们进行性能测试时,我发现后者比前者快了约6倍。

在比较两个代码片段的IL代码后,我发现两种方法是相同的,除了第一个代码片段中的一个castclass指令。

对此感到惊讶,我现在想知道为什么C#编译器在这里不是“聪明”的。事情并不像看起来那么简单,所以为什么C#编译器在这里有点天真呢?


你正在使用哪些编译器选项? - Richard
以发布模式编译(启用了优化代码)。 - Steven
3个回答

33

我的猜测是,您可能发现了优化器中的一个小错误。那里有各种为数组编写的特殊代码。感谢您引起我的注意。


18
只有Eric才能发布一篇回答说“哎呀,我们犯了个错误”,并获得10个赞。太好了!;) - Aaronaught

4
这只是一个粗略的猜测,但我认为它与Array与其通用IEnumerable之间的关系有关。
在.NET Framework 2.0中,Array类实现了System.Collections.Generic.IList、System.Collections.Generic.ICollection和System.Collections.Generic.IEnumerable通用接口。这些实现是在运行时提供给数组的,因此不可见于文档生成工具。因此,在Array类的声明语法中不会出现通用接口,并且没有涉及仅通过将数组强制转换为通用接口类型(显式接口实现)才能访问的接口成员的参考主题。当您将数组强制转换为其中一个这些接口时,需要注意的关键事项是添加、插入或删除元素的成员会抛出NotSupportedException异常。
请参见MSDN文章
不清楚这是否与.NET 2.0+有关,但在这种特殊情况下,如果表达式只在运行时变为有效,则编译器无法优化您的表达式,这就是为什么不清楚的原因。

1
你的猜想并不算太糟,因为这种情况似乎只发生在数组中。当你将 "object[] array = new object[1];" 改成 "Collection<object> array = new Collection<object>();" 时,编译器成功地优化了转换。然而,我并不完全满意,因为虽然文章说“实现是在运行时提供给数组的”,但 C# 编译器实际上知道 T[] 实现了 ICollection<T>。否则,语句 "ICollection<object> col = array;" 就无法编译。我们仍然不知道具体发生了什么。 - Steven

2

这看起来只是编译器在抑制强制类型转换时错过了机会。如果您像这样编写它,它将起作用:

    ICollection<object> col = array as ICollection<object>;

这表明它会变得过于保守,因为类型转换可能会抛出异常。 但是,当您转换为非泛型ICollection时,它确实有效。 我认为他们只是忽视了它。

这里有一个更大的优化问题,JIT编译器没有应用循环不变量提升优化。 它应该像这样重新编写代码:

object[] array = new object[1];
ICollection<object> col = (ICollection<object>)array;
for (int i = 0; i < 100000; i++)
{
    col.Contains(null);
}

例如,在C/C++代码生成器中的标准优化。然而,JIT优化器无法花费大量周期来执行发现此类可能优化所需的分析。好消息是,经过优化的托管代码仍然非常易于调试。并且C#程序员仍然有写出高性能代码的角色。


当谈到JIT时,JIT也没有优化强制转换。正如你已经说过的那样,JIT不这样做是很容易解释的:“JIT优化器不能烧掉太多的周期”。 - Steven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接