C# 编译器不会优化不必要的强制转换
几天前,在溢出时为这个问题写答案时,我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
比较两个片段中的 IL 后,我注意到这两种方法是相同的,除了第一个片段中的 castclass
IL 指令。
对此感到惊讶,我现在想知道为什么 C# 编译器在这里不“智能”。事情从来没有看起来那么简单,那么为什么 C# 编译器在这里有点幼稚呢?
A few days back, while writing an answer for this question here on overflow I got a bit surprised by the C# compiler, who wasn’t doing what I expected it to do. Look at the following to code snippets:
First:
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = (ICollection<object>)array;
col.Contains(null);
}
Second:
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = array;
col.Contains(null);
}
The only difference in code between the two snippets is the cast to ICollection<object>
. Because object[]
implements the ICollection<object>
interface explicitly, I expected the two snippets to compile down to the same IL and be, therefore, identical. However, when running performance tests on them, I noticed the latter to be about 6 times as fast as the former.
After comparing the IL from both snippets, I noticed the both methods were identical, except for a castclass
IL instruction in the first snippet.
Surprised by this I now wonder why the C# compiler isn’t ‘smart’ here. Things are never as simple as it seems, so why is the C# compiler a bit naïve here?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我的猜测是您在优化器中发现了一个小错误。其中有数组的各种特殊情况代码。感谢您引起我的注意。
My guess is that you have discovered a minor bug in the optimizer. There is all kinds of special-case code in there for arrays. Thanks for bringing it to my attention.
这是一个粗略的猜测,但我认为这是关于数组与其通用 IEnumerable 的关系。
请参阅 MSDN 文章。
目前尚不清楚这是否与 .NET 2.0+ 有关,但在这种特殊情况下,如果表达式仅在运行时有效,那么编译器无法优化表达式的原因就很明显了。
This is a rough guess, but i think it's about the Array's relationship to its generic IEnumerable.
See MSDN Article.
It's not clear whether this relates to .NET 2.0+, but in this special case it would make perfect sense why the compiler cannot optimize your expression if it only becomes valid at run time.
这看起来只不过是编译器错过了抑制强制转换的机会。如果你这样写它就会起作用:
这表明它变得过于保守,因为强制转换可能会引发异常。但是,当您转换为非泛型 ICollection 时,它确实有效。我的结论是他们只是忽略了这一点。
这里有一个更大的优化问题,JIT 编译器不应用循环不变提升优化。它应该像这样重写代码:
例如,这是 C/C++ 代码生成器中的标准优化。尽管如此,JIT 优化器无法在发现此类可能的优化所需的分析上消耗大量周期。令人高兴的是,优化的托管代码仍然是相当可调试的。 C# 程序员仍然可以编写高性能代码。
This doesn't look like more than just a missed opportunity in the compiler to suppress the cast. It will work if you write it like this:
which suggests that it gets too conservative because casts can throw exceptions. However, it does work when you cast to the non-generic ICollection. I'd conclude that they simply overlooked it.
There's a bigger optimization issue at work here, the JIT compiler doesn't apply the loop invariant hoisting optimization. It should have re-written the code like this:
Which is a standard optimization in the C/C++ code generator for example. Still, the JIT optimizer can't burn a lot of cycles on the kind of analysis required to discover such possible optimizations. The happy angle on this is that optimized managed code is still quite debuggable. And that there still is a role for the C# programmer to write performant code.