【问题标题】:C# vs C++ ternary operatorC# vs C++ 三元运算符
【发布时间】:2014-01-16 14:31:14
【问题描述】:

我曾经是 Windows 上的 C++ 程序员。 我知道编译器会优化 C++ 中的三元运算符。

C++ 代码:

#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
    int result = argc > 3 ? 1 : 5;
    printf("%d", result);
    return 0;
}

由于管道的关系,生成的原生代码如下图(当然是Release模型):

int result = argc > 3 ? 1 : 5;
00B21003  xor         eax,eax  
00B21005  cmp         dword ptr [argc],3
00B21009  setle       al  
00B2100C  lea         eax,[eax*4+1]

C#代码:

namespace TernaryOperatorCSharp
{
    static void Main(string[] args)
    {
        int argc = args.Length;
        int result = argc > 1 ? 2 : 5;
        System.Console.WriteLine(result);
    }
}

我查了一下JIT生成的native code,但是根本没有优化(还是两条跳转指令)。

int result = argc > 1 ? 2 : 5;
0000002f  cmp         dword ptr [ebp-4],1 
00000033  jg          0000003F 
00000035  nop 
00000036  mov         dword ptr [ebp-0Ch],5 
0000003d  jmp         00000046 
0000003f  mov         dword ptr [ebp-0Ch],2 
00000046  mov         eax,dword ptr [ebp-0Ch] 
00000049  mov         dword ptr [ebp-8],eax 
System.Console.WriteLine(result);
0000004c  mov         ecx,dword ptr [ebp-8] 
0000004f  call        6A423CBC 

为什么 C# JIT 编译器不进行与 C++ 编译器相同的优化?

这背后的故事是什么?

任何信息将不胜感激。


你好, 我已经修改了 C# 程序并使用发布模型运行它。

之前

int result = args.Length > 1 ? 2 : 5;

现在

int argc = args.Length;
int result = argc > 1 ? 2 : 5;

但结果还是一样。 还有两条跳转指令存在。 如果有更多信息,我将不胜感激。

【问题讨论】:

  • 第一个条件大概是处理args == null的情况,或者类似的情况。
  • JIT 也很可能认为没有理由优化此代码,因为它只运行一次。
  • 嗯,这很容易检查,将代码粘贴在一个循环中。
  • 也许答案是 C++ 是混合语言,具有结构类型,并且针对 ?运算符取自 C。而 C# 没有经典类型,int 是类。因此,运算符在该类定义中被重载。当你写 int result = args.Length > 1 ? 2:5;它正在调用 int 类中的方法
  • @RockLegend int 不是一个类,并且运算符没有在该类中定义(它们甚至是如何定义的?),int 是 Special Magic 并且具有针对其运算符的 MSIL 指令(不打电话)。

标签: c# c++ ternary-operator


【解决方案1】:

您没有使用优化进行编译 - nop 指令表明了这一点(编译器插入这些以用作锚点,以便您可以在大括号上放置断点)。

即使您选中了“优化代码”复选框,Visual Studio 也不会始终生成优化代码。通常,当您在调试器中启动时,无论如何它都会禁用优化,以便调试会话的行为更加符合您的预期。

此外,您不是在将苹果与苹果进行比较,这占了更多的麻烦。

string[].Length 是 C# 中的属性,不是公共变量,更不是局部变量。通过使用代码,属性通常被视为公共变量,但实际上可以作为成熟的 get/set 方法存在。编译器必须发出代码来处理这个问题,尤其是当 Property 在单独的程序集中定义时。

尝试使用本地 int 变量并打开编译器优化的示例(使用优化构建,启动程序,启动后附加调试器,查看反汇编)。

【讨论】:

  • 我认为程序在调试器下运行时抖动不会产生优化代码更正确。
  • 还要注意,jitting 是编译时间和运行时间之间的平衡。对抖动添加的优化越多,程序花费在抖动而不做实际工作上的时间就越多。
  • 那么,如果程序安装在gac中,ngen程序可以做更积极的优化。
  • @antiduh 谢谢你的回复。 :) 但我确实使用发布模型制作了 C# 编译器构建代码。
  • @Michael IL 无法表达setX 的概念。将comparison + jump conditional + load 1 + jump + load 2 序列转换为comparison + setX + weird math 是由抖动完成的,并且抖动是非常受时间限制的。鉴于此优化仅适用于整数,抖动的作者很可能认为优化的成本(检测优化可能的时间成本)不会超过其运行时收益。
【解决方案2】:

您正在查看程序的调试版本。切换到 Release 版本。

并且您必须更改一个选项,以便在您使用调试器查看反汇编时不会禁用优化器。工具+选项,调试,常规,取消勾选“Suppress JIT optimization on module load”选项。

您现在将看到更紧凑的代码。 x86 抖动确实执行分支消除并使用 AGU 进行数学运算,您可以在 this answer 中看到它正在完成,只是这里没有。如果您期望与 C 或 C++ 编译器的优化器完全一致,您将会感到失望,因为抖动优化器在运行时运行,所以它在非常严格的时间限制下运行。您可以在this answer 中找到它执行的优化概要。

【讨论】:

  • 谢谢汉斯,我忘了如何强制它始终启用优化,并且没有方便的 VS。
  • @Hans 我已经按照你说的做了,但是编译器不能再调试代码了。
  • 你应该使用调试器代替,编译器只是编译。调试优化代码当然是可能的,只是很尴尬。就像使用 C++ 编译器一样。
猜你喜欢
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-08
  • 2023-03-05
  • 2011-02-15
  • 2011-09-30
相关资源
最近更新 更多