以下哪些代码将针对C / C ++ gcc编译器中的效率第一功能或第二功能进行更优化?
// First Function
if ( A && B && C ) {
UpdateData();
} else if ( A && B ){
ResetData();
}
//Second Function
if ( A && B) {
if (C) {
UpdateData();
} else {
ResetData();
}
}
答案 0 :(得分:2)
这个问题的很大一部分取决于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
换句话说:完全没有区别
使用clang ++ 3.7(大约3周前)与-O1一起使用:
_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
或其他任何东西,NOT调用该函数的结果可能是程序的行为不同。 / p>
(在这种情况下,我只发布了clang代码,但g ++生成的代码结果相同,但不同代码块的顺序略有不同)