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

Posted

技术标签:

【中文标题】C# 中的 ++i 和 i++ 之间是不是存在性能差异?【英文标题】:Is there any performance difference between ++i and i++ in C#?C# 中的 ++i 和 i++ 之间是否存在性能差异? 【发布时间】:2010-10-02 19:29:10 【问题描述】:

使用类似的东西有什么性能差异

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

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

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

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

【问题讨论】:

“主要是学术性的。”这说明了很多。我正在数不清这方面的问题数量,这告诉我“学者”首先提出了这个问题,而不是告诉人们如何不要让事情变得复杂,因此变得缓慢。 这里发布了很好的答案:***.com/a/4706225/214296 只需要最基本的优化器就可以意识到可以省略无用的副作用,例如创建一个不使用的临时 POD ——尤其是在这种非常本地化的上下文中。因此,出于所有实际目的,答案应该是响亮的“不”。但是对于一个非常深入和复杂的理论讨论,有人可能会问++i 是否通过在优化通过之前减少 IR 中的指令数量来减少优化器的工作,或者是否可以先处理 i++ 独立案例发射红外线。从编译时的角度来看,它可能很整洁。 ...虽然从 SSA 的角度来看它变得毫无意义,因为 ++ii++ 都会创建新变量。 Difference between i++ and ++i in a loop?的可能重复 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

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

这就是你想要做的。

从您要查看的内容中抽取几个样本。显然,如果您愿意,您可以对它们进行挂钟计时 - 但我假设您想知道的不止这些。

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

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

您不想在调试器下启动代码,否则 JIT 将生成未优化的代码 - 听起来您想知道它在真实环境中的行为方式。 JIT 这样做是为了最大化调试信息并最小化当前源位置的“跳来跳去”。永远不要在调试器下启动性能评估。

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

我的直觉告诉我,如果您按照您的描述使用 ++i/i++ - 即:在不重复使用右值结果的独立表达式中 - 不会有区别。但是,去发现并查看所有整洁的东西会不会很有趣! :)

【讨论】:

【参考方案3】:

有一段具体的代码和 CLR 版本吗?如果是这样,对其进行基准测试。如果没有,那就算了。微优化等等……此外,您甚至无法确定不同的 CLR 版本会产生相同的结果。

【讨论】:

【参考方案4】:
  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 对于 ++i: 1314

使用 i++ 时:1261 使用 ++i 时:1276

这是 2,53 Ghz 的赛扬 D。每次迭代大约需要 1.6 个 CPU 周期。这要么意味着 CPU 每个周期执行了超过 1 条指令,要么意味着 JIT 编译器展开了循环。 i++ 和 ++i 每次迭代的差异只有 0.01 个 CPU 周期,可能是由后台的 OS 服务造成的。

【讨论】:

查看 Davy Landman 如何在此处(在第二个代码块中)对他的代码进行计时:***.com/questions/419952/… 通过设置进程优先级、亲和性和线程优先级,您可以获得更准确的测量结果。【参考方案5】:

在这种情况下,生成的 ++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 编译器有可能(尽管极不可能)在某些上下文中进行一些优化,从而使一个版本优于另一个版本。但是,如果有这样的优化,它可能只会影响循环的最终(或者可能是第一次)迭代。

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

【讨论】:

++ 是的,没有区别,即使有,循环也必须几乎没有代码才能注意到区别。在此特定代码中,循环开销与循环内容的关系为“闪电虫之于闪电”。 (马克吐温名言) 在 C++ 中, 不同,但预增量更快。不过,我认为 C# 没有任何区别。 @Ysha 不,C++ 没有区别。有关详细信息,请参阅***.com/q/24901/56778。现在,如果它不是一个简单类型被递增的东西,那么可能会有所不同。但是在这个问答的上下文中,没有。【参考方案6】:

如果您提出这个问题,那么您是在尝试解决错误的问题。

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

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

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

我阅读规则 2 的意思是“首先编写满足客户需求的干净、清晰的代码,然后在速度过慢的地方加快速度”。 ++ii++ 不太可能成为解决方案。

【讨论】:

太棒了,不是吗?最好的回应在这里——也是唯一必要的。 我从来没有想过“哪个更快”的问题。我总是使用++i。为什么?因为我读取 从左到右 并且++i 被读取为“增加i 的值”而不是“采用i 并增加它的值”。由于++ 在左侧,我可以立即看到发生了什么,我不需要将我的眼球移到右侧:) 如果你有一个长变量名,++something 更具可读性。跨度> -1:回答 OP 从未问过的问题,而忽略了真正的问题。也称为 OP 巨魔。 好吧,也许这不是关于一些具体循环优化的问题,而是关于编码习惯的问题。如果您养成了使您的代码在所有循环中更加优化的好习惯,为什么不这样做呢?例如,C++ 中的 STL 使用迭代器,与迭代器 ++ 相比,它们中的许多作为 ++iterator 工作得更快。在 C++ 中随处写 ++i 毫无价值。但似乎 C# 中的 ++ 仅用于整数类型,因此 C# 中的 ++i 或 i++ 无关紧要。【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

好的 C++ 答案,非常糟糕的 C# 答案。 IIRC,C# 没有单独的 preincrement 和 postincrement 重载。 @Ben Voigt:是的,C# 中的增量运算符只能有一个重载 (msdn.microsoft.com/en-us/library/aa691363.aspx)。我不知道。感谢您的提示。【参考方案9】:

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

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

如下所示,

步骤初始化:i = 0

步比较:i &lt; 3,为真则执行loop

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

Step Copmare Again : 由于 Java 在下一步中进行比较。 i++ 和 ++i 都给出相同的结果。例如,如果 i = 0,则递增后 i 将变为 1。

这就是为什么前增量或后增量在循环中的行为相同。

【讨论】:

虽然答案可能相同,但这是一个 C# 问题,而不是 Java。此外,i = i++ 实际上是一个空操作,与i++ 不同。

以上是关于C# 中的 ++i 和 i++ 之间是不是存在性能差异?的主要内容,如果未能解决你的问题,请参考以下文章

不将引用类型复制到 C# 中的每个线程是不是存在性能劣势? [关闭]

c#中的for和while循环

c#中(&&,)与(&,)的区别和共同点

在c#中使用for循环检查列表中是不是存在字符串

C#类继承与重载的问题,Mat继承于Matrix,但提示无法将类型隐式转换为 存在一个显式转换 是不是缺少强制转

NodePHPJava 和 Go 服务端 I/O 性能PK