for(int i = 0; i < 10; i++) { ... }
和
for(int i = 0; i < 10; ++i) { ... }
使用的东西之间的性能差异或者是编译器能够以这样的方式,他们的情况一样快,他们在功能上等同于优化?
编辑: 这个问题是因为我和同事讨论过这个问题,并不是因为我认为它在任何实际意义上都是有用的优化。它主要是学术性的。
for(int i = 0; i < 10; i++) { ... }
和
for(int i = 0; i < 10; ++i) { ... }
使用的东西之间的性能差异或者是编译器能够以这样的方式,他们的情况一样快,他们在功能上等同于优化?
编辑: 这个问题是因为我和同事讨论过这个问题,并不是因为我认为它在任何实际意义上都是有用的优化。它主要是学术性的。
在这种情况下,生成的++ 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编译器可以做在某些情况下一些优化这将有利于一个版本的优势。但是,如果有这样的优化,它可能只会影响循环的最终(或者可能是第一次)迭代。
简而言之,在所描述的循环构造中,控制变量的简单预增量或后增量的运行时间没有区别。
根据this answer,i ++使用超过++ i的一个CPU指令。但是,这是否会导致性能差异,我不知道。
由于任何一个循环都可以轻松地重写为使用后增量或预增量,所以我猜编译器总是会使用更高效的版本。
伙计们,“答案”是针对C和C++的。
C#是一种不同的动物。
使用ILDASM查看编译输出以验证是否存在MSIL差异。
啊...再次打开。好。这笔交易。
ILDASM是一个开始,但不是结束。关键是:JIT为汇编代码生成什么?
这就是你想要做的。
拿一些你想要看的样品。显然,如果你愿意,你可以挂钟 - 但我想你想知道更多。
以下是不明显的。 C#编译器会在很多情况下生成一些非最优的MSIL序列。它调整了JIT来处理来自其他语言的这些和怪癖。问题:只有人注意到的'怪癖'已被调整。
你真的想制作一个样本,让你的实现尝试,返回到主要(或任何地方),睡眠(),或者你可以附加调试器的东西,然后再次运行例程。
你不想在调试器下启动代码,否则JIT将生成非优化代码 - 听起来你想知道它在真实环境中的表现。 JIT这样做是为了最大限度地提高调试信息的质量,并将当前源位置从“跳来跳去”最小化。切勿在调试器下启动perf评估。
好的。所以一旦代码运行一次(即:JIT已经为它生成代码),那么在睡眠期间(或其他)连接调试器。然后看看为这两个例程生成的x86/x64。
我的直觉告诉我,如果你正在使用++ i/i ++,就像你描述的那样 - 即:在独立表达式中右值结果不被重用 - 不会有区别。但找到并看到所有整齐的东西不是很有趣! :)
有一个具体的代码和CLR释放记?如果是这样,请以此为基准如果没有,忘记它。微型优化,以及所有这些...此外,你甚至不能确定不同的CLR版本会产生相同的结果。
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服务引起的。
如果你问这个问题,你试图解决错误的问题。
要问的第一个问题是“如何通过使其运行速度更快来提高客户对我的软件的满意度?”答案几乎从来没有“使用++我而不是i ++”,反之亦然。
从编码恐怖的文章 “Hardware is Cheap, Programmers are Expensive”:
规则优化:
规则1:不要做。
规则2(仅适用于专家):不要这样做。
- M.A. Jackson
我读第2条是指“满足客户的需求,第一次写干净,清晰的代码,然后加速它的地方太慢了”。 ++i
与i++
即将成为解决方案的可能性极小。
作为Jim Mischel has shown,编译器将为编写for循环的两种方式生成相同的MSIL。
但是那就是:没有理由推测JIT或执行速度测量。如果两行代码生成相同的MSIL,则不仅它们的性能相同,而且它们实际上是相同的。
没有可能的JIT将能够区分循环,所以生成的机器代码也必须是相同的。
除了其他答案,如果您的i
不是int
,可能会有差异。 在C++中,如果它是运算子++()
和++(int)
重载的运算符类的对象,则它可能会产生差异,并可能产生副作用。性能++i
应该在这种情况下更好(取决于实施)。
这应该没有关闭。但无论如何,如果你编译它并使用ildasm.exe来查看MSIL,你会看到这两个例子导致相同的MSIL。 – 2009-01-21 22:50:21
投票重新开放。致John Sheehan,tvanfosson,ctacke,Marc Gravell ..如果您投票结束,请编辑并提供问题中的副本!我看到没有链接到一个骗局 – mmcdole 2009-01-21 22:54:10
有一个与重复的答案,我不知道它去了哪里。 – 2009-01-21 23:28:31