在C#中,++ i和i ++之间有任何性能差异吗?
使用类似的东西有没有任何性能差异
for(int i = 0; i < 10; i++) { ... }
和
for(int i = 0; i < 10; ++i) { ... }
或者编译器是否能够在function上等同的情况下以同样快的速度进行优化?
编辑:这是因为我和同事讨论过这个问题,而不是因为我认为它在任何实际意义上都是有用的优化。 这主要是学术。
在这种情况下,生成的++ 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编译器可能(尽pipe极不可能)在某些情况下可以做一些优化,而这些优化将会使一个版本胜过另一个版本。 如果有这样的优化,那么它可能只会影响循环的最后一个(也许是第一个)迭代。
简而言之,在所描述的循环构造中,控制variables的简单预增量或后增量的运行时间没有区别。
啊…再次打开。 好。 这是交易。
ILDASM是一个开始,但不是结束。 关键是:JIT为汇编代码生成什么?
这是你想要做的。
拿一些你正在试图看的样本。 显然,如果你愿意的话,你可以挂钟 – 但是我想你想知道更多。
以下是不明显的。 C#编译器会在很多情况下生成一些非最优的MSIL序列。 它调整了JIT来处理这些和其他语言的怪癖。 问题:只有人注意到的“怪癖”已经被调整了。
你真的想做一个样本,让你的实现尝试,返回到主要(或任何地方),睡眠(),或者你可以附加一个debugging器的东西,然后再次运行例程。
您不希望在debugging器下启动代码,或者JIT将生成非优化的代码 – 听起来好像您想知道它将如何在真实环境中运行。 JIT这样做是为了最大限度地提高debugging信息的质量,并将当前的源位置从“跳来跳去”最小化。 切勿在debugging器下启动perf评估。
好。 所以一旦代码运行一次(即:JIT已经为其生成了代码),那么在睡眠期间(或其他)连接debugging器。 然后看看为这两个例程生成的x86 / x64。
我的直觉告诉我,如果你正在使用++ i / i ++,就像你所描述的那样:即在一个独立的expression式中右值结果没有被重用,那么就没有区别。 但是,去发现并看到所有整洁的东西是不是很有趣? 🙂
伙计们,这些“答案”是针对C和C ++的。
C#是一个不同的动物。
使用ILDASM来查看编译的输出来validation是否有一个MSIL差异。
有一个具体的代码和CLR释放记? 如果是这样,请以此为基准 如果没有,忘记它。 微优化,以及所有这些…此外,你甚至不能确定不同的CLR版本会产生相同的结果。
正如Jim Mischel 所示 ,编译器会为写for循环的两种方式生成相同的MSIL。
但那就是:没有理由推测JIT或进行速度测量。 如果两行代码生成相同的MSIL,则不仅它们的性能相同,而且实际上是相同的。
没有可能的JIT能够区分这些循环,所以生成的机器代码也必须是相同的。
如果你问这个问题,你试图解决错误的问题。
要问的第一个问题是“如何通过提高运行速度来提高客户对我的软件的满意度?” 答案是几乎从来没有“使用++我而不是i ++”,反之亦然。
从编码恐怖的post“ 硬件便宜,程序员是昂贵的 ”:
优化规则:
规则1:不要这样做。
规则2(仅适用于专家):不要这样做。
– 马克·jackson
我阅读规则2意味着“首先写清楚,清晰的代码,以满足您的客户的需求,然后加快它的速度太慢”。 ++i
和i++
是不太可能的解决scheme。
除了其他的答案之外, 如果你的i
不是一个int
那么你可能会有所不同。 在C ++中 ,如果它是一个具有运算符++()
和++(int)
的类的对象,那么它可能会有所作为,并且可能是副作用。 ++i
性能++i
应该在这种情况下更好(取决于实现)。
根据这个答案 ,i ++使用比++ i多一个CPU指令。 但是,这是否会导致性能差异,我不知道。
由于任何一个循环都可以很容易地被重写为使用后增量或者预增量,所以我猜测编译器总是会使用更高效的版本。
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
这是一个2.53 GHz的赛扬D。 每次迭代大约需要1.6个CPU周期。 这意味着CPU每个周期执行多于1个指令,或者JIT编译器展开循环。 i ++和++ i之间的区别是每次迭代只有0.01个CPU周期,可能是由后台的OS服务引起的。