2009-01-21 89 views
40

有没有像C#中++ i和i ++之间有任何性能差异吗?

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

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

使用的东西之间的性能差异或者是编译器能够以这样的方式,他们的情况一样快,他们在功能上等同于优化?

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

+1

这应该没有关闭。但无论如何,如果你编译它并使用ildasm.exe来查看MSIL,你会看到这两个例子导致相同的MSIL。 – 2009-01-21 22:50:21

+0

投票重新开放。致John Sheehan,tvanfosson,ctacke,Marc Gravell ..如果您投票结束,请编辑并提供问题中的副本!我看到没有链接到一个骗局 – mmcdole 2009-01-21 22:54:10

+0

有一个与重复的答案,我不知道它去了哪里。 – 2009-01-21 23:28:31

回答

34

在这种情况下,生成的++ 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编译器可以做在某些情况下一些优化这将有利于一个版本的优势。但是,如果有这样的优化,它可能只会影响循环的最终(或者可能是第一次)迭代。

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

0

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

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

4

伙计们,“答案”是针对C和C++的。

C#是一种不同的动物。

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

7

啊...再次打开。好。这笔交易。

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

这就是你想要做的。

拿一些你想要看的样品。显然,如果你愿意,你可以挂钟 - 但我想你想知道更多。

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

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

你不想在调试器下启动代码,否则JIT将生成非优化代码 - 听起来你想知道它在真实环境中的表现。 JIT这样做是为了最大限度地提高调试信息的质量,并将当前源位置从“跳来跳去”最小化。切勿在调试器下启动perf评估。

好的。所以一旦代码运行一次(即:JIT已经为它生成代码),那么在睡眠期间(或其他)连接调试器。然后看看为这两个例程生成的x86/x64。

我的直觉告诉我,如果你正在使用++ i/i ++,就像你描述的那样 - 即:在独立表达式中右值结果不被重用 - 不会有区别。但找到并看到所有整齐的东西不是很有趣! :)

3

有一个具体的代码和CLR释放记?如果是这样,请以此为基准如果没有,忘记它。微型优化,以及所有这些...此外,你甚至不能确定不同的CLR版本会产生相同的结果。

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个运行平均:
与我++:1307 与++我:1314

而与我++:1261 同时用++我:1276

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

2

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

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

从编码恐怖的文章 “Hardware is Cheap, Programmers are Expensive”:

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

我读第2条是指“满足客户的需求,第一次写干净,清晰的代码,然后加速它的地方太慢了”。 ++ii++即将成为解决方案的可能性极小。

3

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

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

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

2

除了其他答案,如果您的i不是int,可能会有差异在C++中,如果它是运算子++()++(int)重载的运算符类的对象,则它可能会产生差异,并可能产生副作用。性能++i应该在这种情况下更好(取决于实施)。

相关问题