我在思考C/C++中以下两种语句是否有性能差异:
情况1:
if (p==0)
do_this();
else if (p==1)
do_that();
else if (p==2)
do_these():
情况2:
if(p==0)
do_this();
if(p==1)
do_that();
if(p==2)
do_these();
假设简单类型(在本例中,我使用了int
)并且没有什么奇怪的操作(没有重新定义int
的operator=),至少在AMD64架构上运行的GCC 4.6中,这两者没有区别。生成的代码是相同的:
0000000000000000 <case_1>: 0000000000000040 <case_2>:
0: 85 ff test %edi,%edi 40: 85 ff test %edi,%edi
2: 74 14 je 18 <case_1+0x18> 42: 74 14 je 58 <case_2+0x18>
4: 83 ff 01 cmp $0x1,%edi 44: 83 ff 01 cmp $0x1,%edi
7: 74 27 je 30 <case_1+0x30> 47: 74 27 je 70 <case_2+0x30>
9: 83 ff 02 cmp $0x2,%edi 49: 83 ff 02 cmp $0x2,%edi
c: 74 12 je 20 <case_1+0x20> 4c: 74 12 je 60 <case_2+0x20>
e: 66 90 xchg %ax,%ax 4e: 66 90 xchg %ax,%ax
10: f3 c3 repz retq 50: f3 c3 repz retq
12: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 52: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
18: 31 c0 xor %eax,%eax 58: 31 c0 xor %eax,%eax
1a: e9 00 00 00 00 jmpq 1f <case_1+0x1f> 5a: e9 00 00 00 00 jmpq 5f <case_2+0x1f>
1f: 90 nop 5f: 90 nop
20: 31 c0 xor %eax,%eax 60: 31 c0 xor %eax,%eax
22: e9 00 00 00 00 jmpq 27 <case_1+0x27> 62: e9 00 00 00 00 jmpq 67 <case_2+0x27>
27: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 67: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
2e: 00 00 6e: 00 00
30: 31 c0 xor %eax,%eax 70: 31 c0 xor %eax,%eax
32: e9 00 00 00 00 jmpq 37 <case_1+0x37> 72: e9 00 00 00 00 jmpq 77 <case_2+0x37>
37: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
3e: 00 00
case_1末尾的额外指令只是用于填充(以便对齐下一个函数)。
这并不奇怪,因为确定在该函数中未更改p是一种相当基本的优化。如果p可以被更改(例如通过引用传递或作为指向各个do_...
函数的指针,或者p本身是引用或指针,因此可能存在别名),则其行为是不同的,生成的代码也会不同。
int
的operator==
(或operator=
)。 - L. F.在前一种情况下,匹配后的条件不会被评估。
==
不能被重载),我不会感到惊讶,因为编译器可以推断只有一个条件是真的(假设if-branch不修改正在测试的值)。也就是说,假设两个版本的结果相同且类型是基本类型,编译器可以将它们都转换为switch。 - David Rodríguez - dribeasp
...(并尝试证明指向 p
的指针没有被存储在磁盘上,并且可以被这些函数检索)。 - Kerrek SBdo_this()
,则可以从另一个方面来处理这个问题,这就是为什么严格的别名规则很重要--如果 p
是一个 int
,而 do_this()
仅通过指针修改 float
变量,则编译器可以假设 do_this()
不会修改 p
。 - Steve Jessopif else 更快; 如果在最后一个if之前找到了匹配项,则至少跳过最后一个if语句;如果在第一个if中找到匹配项,则会跳过所有其他语句。
if if 较慢; 即使使用第一个if语句找到了匹配项,它仍将继续尝试在其他语句中进行匹配。
if (p == 0)
成立,那么 p
不能有其他值,所以这些测试会被跳过。 - Bo Perssonif
代码块与 if-else
相同。 - its4zahoor正如已经展示的那样……它是多变的。
如果我们谈论原始(内置)类型,比如int
,那么编译器可能足够智能,使得它不重要(或者不是)。无论如何,性能影响将很小,因为调用函数的成本比一个if
语句高得多,所以如果你试图衡量它,差异很可能会在噪声中消失。
然而,语义上有很大的区别。
当我看到第一个例子时:
if (...) {
// Branch 1
} else if (...) {
// Branch 2
}
那么我知道无论这两个分支中的哪一个被执行,只会有一个被执行。
然而,当我阅读第二种情况:
if (...) {
}
if (...) {
}
if..if..if
需要检查每一个表达式。如果单个表达式是互斥的,您可以使用 if..else if..
来节省这种评估。这样只有在前面的情况失败时,才会检查其他表达式。switch
语句。这样,即使对长序列进行检查,您仍然可以保持一定程度的可读性。switch ( p )
{
case 0:
do_this();
break;
case 1:
do_that();
break;
case 2:
do_these():
break;
}
如果..如果情况可以通过在所有后续的IF检查中使用DONE标志来改进(因为真实逻辑是从左到右评估的),以避免重复工作/匹配和优化。
bool bDone = false;
If( <condition> ) {
<work>;
bDone = true;
}
if (!bDone && <condition> ) {
<work>;
bDone = true;
}
或者可以使用类似这样的逻辑:
While(true) {
if( <condition> ) {
<work>;
break;
}
if( <condition> ) {
<work>;
break;
}
....
break;
}
虽然阅读起来有些混乱(问“为什么要这样做”)
do_this()
或者do_that()
期间,p
值没有发生改变,否则代码总是执行3次检查。如果在if/if的情况下,并且假设没有重载操作符,那么优化器可以意识到,如果p==0
为真,那么p==1
不可能也为真,并跳过对它的评估,就像代码是if/else一样。抽象机执行了3次检查,但实现是否这样做是另外一回事。 - Steve Jessop
p
重载了operator==
,始终返回true
,而do_that()
将所有的钱都发送给了你的前妻。 - Kerrek SB