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

发布于 2024-08-20 08:43:34 字数 888 浏览 2 评论 0原文

几天前,在溢出时为这个问题写答案时,我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[] 显式实现了 ICollection接口,因此我希望这两个代码段能够编译为相同的 IL,因此是相同的。然而,当对它们进行性能测试时,我注意到后者的速度大约是前者的 6 倍。

比较两个片段中的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

晨敛清荷 2024-08-27 08:43:34

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

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.

晒暮凉 2024-08-27 08:43:34

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

在 .NET Framework 2.0 版中,
数组类实现了
System.Collections.Generic.IList,
System.Collections.Generic.ICollection,

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

请参阅 MSDN 文章

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

This is a rough guess, but i think it's about the Array's relationship to its generic IEnumerable.

In the .NET Framework version 2.0, the
Array class implements the
System.Collections.Generic.IList,
System.Collections.Generic.ICollection,
and
System.Collections.Generic.IEnumerable
generic interfaces. The
implementations are provided to arrays
at run time, and therefore are not
visible to the documentation build
tools. As a result, the generic
interfaces do not appear in the
declaration syntax for the Array
class, and there are no reference
topics for interface members that are
accessible only by casting an array to
the generic interface type (explicit
interface implementations). The key
thing to be aware of when you cast an
array to one of these interfaces is
that members which add, insert, or
remove elements throw
NotSupportedException.

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.

绾颜 2024-08-27 08:43:34

这看起来只不过是编译器错过了抑制强制转换的机会。如果你这样写它就会起作用:

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

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:

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

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:

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文