C#编译器不会优化不必要的强制转换

时间:2010-02-07 12:26:43

标签: c# performance compiler-construction cil

几天前,当我在溢出时为this question写一个答案时,我对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&lt; object&gt;的强制转换。因为object []实现了ICollection&lt; object&gt;接口显式,我期望两个片段编译成相同的IL,因此是相同的。但是,当对它们进行性能测试时,我注意到后者的速度是前者的6倍。

在比较两个片段中的IL之后,我注意到两种方法都是相同的,除了第一个片段中的 castclass IL指令。

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

3 个答案:

答案 0 :(得分:32)

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

答案 1 :(得分:4)

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

  

在.NET Framework 2.0版中,   Array类实现了   System.Collections.Generic.IList,   了System.Collections.Generic.ICollection,   和   System.Collections.Generic.IEnumerable   通用接口。该   实现被提供给数组   在运行时,因此不是   对文档构建可见   工具。结果,通用   接口不会出现在   Array的声明语法   上课,并没有参考   接口成员的主题   只能通过将数组转换为   通用接口类型(显式   接口实现)。钥匙   当你施展时要注意的事情   数组到这些接口之一是   添加,插入或的成员   删除元素抛出   NotSupportedException异常。

请参阅MSDN Article

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

答案 2 :(得分: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#程序员仍然可以编写高性能代码。