C#中的++ i和i ++之间是否存在任何性能差异?

时间:2009-01-21 22:33:13

标签: c# operators performance

使用

之类的内容是否有任何性能差异
for(int i = 0; i < 10; i++) { ... }

for(int i = 0; i < 10; ++i) { ... }

或者编译器是否能够以这样的方式进行优化:在功能相同的情况下它们同样快速?

编辑: 这是因为我与同事讨论过这个问题,而不是因为我认为它在任何实际意义上都是有用的优化。它主要是学术性的。

10 个答案:

答案 0 :(得分:36)

在这种情况下,生成的++ i和i ++中间代码没有区别。鉴于此计划:

class Program
{
    const int counter = 1024 * 1024;
    static void Main(string[] args)
    {
        for (int i = 0; i < counter; ++i)
        {
            Console.WriteLine(i);
        }

        for (int i = 0; i < counter; i++)
        {
            Console.WriteLine(i);
        }
    }
}

两个循环生成的IL代码相同:

  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  // Start of first loop
  IL_0002:  ldc.i4.0
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0010
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000c:  ldloc.0
  IL_000d:  ldc.i4.1
  IL_000e:  add
  IL_000f:  stloc.0
  IL_0010:  ldloc.0
  IL_0011:  ldc.i4     0x100000
  IL_0016:  blt.s      IL_0006
  // Start of second loop
  IL_0018:  ldc.i4.0
  IL_0019:  stloc.0
  IL_001a:  br.s       IL_0026
  IL_001c:  ldloc.0
  IL_001d:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0022:  ldloc.0
  IL_0023:  ldc.i4.1
  IL_0024:  add
  IL_0025:  stloc.0
  IL_0026:  ldloc.0
  IL_0027:  ldc.i4     0x100000
  IL_002c:  blt.s      IL_001c
  IL_002e:  ret

也就是说,JIT编译器可以(但不太可能)在某些上下文中进行一些优化,这些上下文有利于一个版本而不是另一个版本。但是,如果存在这样的优化,它可能只会影响循环的最终(或可能是第一次)迭代。

简而言之,在您描述的循环结构中,控制变量的简单预增量或后增量的运行时间没有区别。

答案 1 :(得分:7)

啊......再打开一次。好。这是交易。

ILDASM是一个开始,但不是结束。关键是:JIT将为汇编代码生成什么?

这是你想要做的。

对您要查看的内容进行一些示例。显然,如果你愿意,你可以按时间计时 - 但我认为你想了解更多。

这是不明显的。 C#编译器会生成一些在很多情况下都不是最优的MSIL序列。它调整的JIT用于处理来自其他语言的这些和怪癖。问题是:只有有人注意到的“怪癖”才会被调整。

你真的想要制作一个样本,让你的实现尝试,返回到main(或任何地方),Sleep(),或者你可以附加调试器的东西,然后再次运行例程。

您不想在调试器下启动代码,或者JIT会生成非优化代码 - 听起来您想知道它在真实环境中的行为方式。 JIT执行此操作以最大化调试信息并最小化当前源位置“跳转”。切勿在调试器下启动性能评估。

行。因此,一旦代码运行一次(即:JIT已为其生成代码),则在睡眠期间(或其他)附加调试器。然后查看为这两个例程生成的x86 / x64。

我的直觉告诉我,如果你正在使用如你所描述的++ i / i ++ - 即:在一个独立的表达式中,rvalue结果不会被重复使用 - 那就没有区别了。但是去看看所有整洁的东西不是很有趣! :)

答案 2 :(得分:7)

如果你问这个问题,那你就试图解决错误的问题。

要问的第一个问题是“如何通过让我的软件运行得更快来提高客户对软件的满意度?”答案几乎从不“使用++ i代替i ++”,反之亦然。

来自Coding Horror的帖子“Hardware is Cheap, Programmers are Expensive”:

  

优化规则:
  规则1:不要这样做   规则2(仅限专家):不要这样做。
   - M.A. Jackson

我将规则2理解为“首先编写干净,清晰的代码以满足客户的需求,然后在速度太慢的情况下加速”。 <{1}}与++i之间的解决方案极不可能。

答案 3 :(得分:5)

作为Jim Mischel has shown,编译器将为两种写入for循环的方式生成相同的MSIL。

但那就是它:没有理由推测JIT或进行速度测量。如果两行代码生成相同的MSIL,它们不仅执行相同,而且它们实际上是相同的。

没有可能JIT能够区分循环,因此生成的机器代码也必须是相同的。

答案 4 :(得分:4)

伙计们,伙计们,“答案”适用于C和C ++。

C#是另一种动物。

使用ILDASM查看已编译的输出以验证是否存在MSIL差异。

答案 5 :(得分:3)

有一段具体的代码和CLR版本吗?如果是这样,请对其进如果没有,请忘掉它。微优化,以及所有......除此之外,你甚至不能确定不同的CLR版本会产生相同的结果。

答案 6 :(得分:2)

除了其他答案之外,如果您的i不是int ,则会有所不同。 在C ++ 中,如果它是一个类的对象,其中运算符++()++(int)被重载,那么它可能会产生影响,并可能产生副作用。在这种情况下,++i 的性能应该更好(取决于实现)。

答案 7 :(得分:0)

根据this answer,i ++比++ i使用一条CPU指令。但是,这是否会导致性能差异,我不知道。

由于任何一个循环都可以轻松地重写以使用后增量或预增量,我猜编译器将始终使用更高效的版本。

答案 8 :(得分:0)

  static void Main(string[] args) {
     var sw = new Stopwatch(); sw.Start();
     for (int i = 0; i < 2000000000; ++i) { }
     //int i = 0;
     //while (i < 2000000000){++i;}
     Console.WriteLine(sw.ElapsedMilliseconds);

3次运行的平均值:
与i ++:1307 for ++ ++:1314

与i ++:1261 与++ i:1276

这是赛扬D在2,53 Ghz。每次迭代大约需要1.6个CPU周期。这或者意味着CPU每个周期执行多于1个指令,或者JIT编译器展开循环。 i ++和++ i之间的差异是每次迭代只有0.01个CPU周期,可能是由后台的OS服务引起的。

答案 9 :(得分:0)

两者之间没有区别, 原因是循环比较是在增量/减量语句之后的单独语句。 示例;

for(int i=0; i<3; i++){
    System.out.println(i)
}

如下所示,

步骤初始化:i = 0

Step Compare:i < 3,为true,然后执行loop

步骤增量:i = i++;i = ++i;

再次执行Copmare:随着Java在下一步中进行比较。 i ++和++ i给出相同的结果。例如,如果i = 0,则增量后我将变为1。

这就是为什么前增量或后增量在循环中表现相同的原因。