这个问题的很大一部分将取决于A、B 和C 到底是什么(编译器会对其进行优化,如下所示)。简单的类型,绝对不值得担心。如果它们是某种“大数数学”对象,或者是某种复杂的数据类型,每个需要 1000 条指令“这是true 还是不是”,那么如果编译器决定编写不同的代码,就会有很大的不同。
与以往一样,在性能方面:在您自己的代码中进行测量,使用分析来检测代码花费最多时间的位置,然后通过对该代码的更改进行测量。重复直到它运行得足够快[不管那是什么]和/或你的经理告诉你停止摆弄代码。然而,通常情况下,除非它确实是代码的高流量区域,否则重新安排 if 语句中的条件几乎没有什么区别,它是在一般情况下产生最大影响的整体算法。
如果我们假设 A、B 和 C 是简单的类型,比如int,我们可以写一些代码来调查:
extern int A, B, C;
extern void UpdateData();
extern void ResetData();
void func1()
{
if ( A && B && C ) {
UpdateData();
} else if ( A && B ){
ResetData();
}
}
void func2()
{
if ( A && B) {
if (C) {
UpdateData();
} else {
ResetData();
}
}
}
gcc 4.8.2 给出这个,使用 -O1 产生这个代码:
_Z5func1v:
cmpl $0, A(%rip)
je .L6
cmpl $0, B(%rip)
je .L6
subq $8, %rsp
cmpl $0, C(%rip)
je .L3
call _Z10UpdateDatav
jmp .L1
.L3:
call _Z9ResetDatav
.L1:
addq $8, %rsp
.L6:
rep ret
_Z5func2v:
.LFB1:
cmpl $0, A(%rip)
je .L12
cmpl $0, B(%rip)
je .L12
subq $8, %rsp
cmpl $0, C(%rip)
je .L9
call _Z10UpdateDatav
jmp .L7
.L9:
call _Z9ResetDatav
.L7:
addq $8, %rsp
.L12:
rep ret
换句话说:完全没有区别
使用带有 -O1 的 clang++ 3.7(截至大约 3 周前)给出以下结果:
_Z5func1v: # @_Z5func1v
cmpl $0, A(%rip)
setne %cl
cmpl $0, B(%rip)
setne %al
andb %cl, %al
movzbl %al, %ecx
cmpl $1, %ecx
jne .LBB0_2
movl C(%rip), %ecx
testl %ecx, %ecx
je .LBB0_2
jmp _Z10UpdateDatav # TAILCALL
.LBB0_2: # %if.else
testb %al, %al
je .LBB0_3
jmp _Z9ResetDatav # TAILCALL
.LBB0_3: # %if.end8
retq
_Z5func2v: # @_Z5func2v
cmpl $0, A(%rip)
je .LBB1_4
movl B(%rip), %eax
testl %eax, %eax
je .LBB1_4
cmpl $0, C(%rip)
je .LBB1_3
jmp _Z10UpdateDatav # TAILCALL
.LBB1_4: # %if.end4
retq
.LBB1_3: # %if.else
jmp _Z9ResetDatav # TAILCALL
.Ltmp1:
在 clang 的 func1 中链接和可能是有益的,但它可能是一个很小的差异,你应该专注于从代码的逻辑角度来看更有意义的地方。
总结:不值得
g++ 中更高的优化使其执行与 clang 相同的尾调用优化,否则没有区别。
但是,如果我们将A、B 和C 变成编译器无法“理解”的外部函数,那么我们就会有所不同:
_Z5func1v: # @_Z5func1v
pushq %rax
.Ltmp0:
.cfi_def_cfa_offset 16
callq _Z1Av
testl %eax, %eax
je .LBB0_3
callq _Z1Bv
testl %eax, %eax
je .LBB0_3
callq _Z1Cv
testl %eax, %eax
je .LBB0_3
popq %rax
jmp _Z10UpdateDatav # TAILCALL
.LBB0_3: # %if.else
callq _Z1Av
testl %eax, %eax
je .LBB0_5
callq _Z1Bv
testl %eax, %eax
je .LBB0_5
popq %rax
jmp _Z9ResetDatav # TAILCALL
.LBB0_5: # %if.end12
popq %rax
retq
_Z5func2v: # @_Z5func2v
pushq %rax
.Ltmp2:
.cfi_def_cfa_offset 16
callq _Z1Av
testl %eax, %eax
je .LBB1_4
callq _Z1Bv
testl %eax, %eax
je .LBB1_4
callq _Z1Cv
testl %eax, %eax
je .LBB1_3
popq %rax
jmp _Z10UpdateDatav # TAILCALL
.LBB1_4: # %if.end6
popq %rax
retq
.LBB1_3: # %if.else
popq %rax
jmp _Z9ResetDatav # TAILCALL
在这里我们确实看到了func1 和func2 之间的区别,其中func1 将调用A 和B 两次——因为编译器不能假设调用这些函数ONCE 会做同样的事情作为调用两次。 [考虑到函数A 和B 可能正在从文件中读取数据,调用rand,或者其他什么,不调用该函数的结果可能是程序的行为不同。
(在这种情况下,我只发布了 clang 代码,但 g++ 生成的代码具有相同的结果,但不同代码块的顺序略有不同)