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

问题描述 投票:0回答:3

几天前,在写关于溢出的这个问题的答案时,我对 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# 编译器在这里有点幼稚呢?

c# performance optimization cil
3个回答
33
投票

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


4
投票

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

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

请参阅 MSDN 文章

尚不清楚这是否与 .NET 2.0+ 有关,但在这种特殊情况下,如果表达式仅在运行时有效,那么编译器无法优化表达式的原因就很明显了。


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# 程序员仍然可以编写高性能代码。

© www.soinside.com 2019 - 2024. All rights reserved.