31

几天前,当我在溢出时为这个问题写一个答案时,我对 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第一个片段中的 IL 指令。

对此感到惊讶,我现在想知道为什么 C# 编译器在这里不“聪明”。事情从来没有看起来那么简单,那么为什么 C# 编译器在这里有点幼稚呢?

4

3 回答 3

33

我的猜测是您在优化器中发现了一个小错误。那里有各种用于数组的特殊情况代码。感谢您引起我的注意。

于 2010-02-07T15:21:34.823 回答
4

这是一个粗略的猜测,但我认为这是关于数组与其通用 IEnumerable 的关系。

在 .NET Framework 2.0 版中,Array 类实现 System.Collections.Generic.IList、System.Collections.Generic.ICollection 和 System.Collections.Generic.IEnumerable 泛型接口。这些实现在运行时提供给数组,因此对文档构建工具不可见。因此,泛型接口不会出现在 Array 类的声明语法中,并且没有接口成员的参考主题,只能通过将数组转换为泛型接口类型(显式接口实现)才能访问这些成员。将数组强制转换为这些接口之一时要注意的关键是添加、插入或删除元素的成员会抛出 NotSupportedException。

请参阅MSDN 文章

目前尚不清楚这是否与 .NET 2.0+ 相关,但在这种特殊情况下,如果表达式仅在运行时有效,编译器无法优化您的表达式是完全可以理解的。

于 2010-02-07T12:56:08.330 回答
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# 程序员仍然需要编写高性能代码。

于 2010-02-07T15:50:18.600 回答