if-else if梯形图和编译器优化

时间:2015-02-14 15:19:16

标签: c++ c gcc

以下哪些代码将针对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();
    }
}
  1. 我们是否在第二功能
  2. 中获得了任何性能提升
  3. 如果使用第一个功能,编译器可以自行将其优化为第二个方法吗?

1 个答案:

答案 0 :(得分:2)

这个问题的很大一部分取决于ABC到底是什么(编译器会优化它,如下所示)。简单的类型,绝对不值得担心。如果它们是某种“大数学数学”对象,或者某些需要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相同的尾调优化,否则没有区别。

但是,如果我们将ABC放入编译器无法“理解”的外部函数中,那么我们就会有所不同:

_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

我们在这里看到func1func2之间的区别,其中func1将调用AB两次 - 因为编译器无法假设调用那些函数ONCE将执行与调用两次相同的操作。 [考虑函数AB可能正在从文件中读取数据,调用rand或其他任何东西,NOT调用该函数的结果可能是程序的行为不同。 / p>

(在这种情况下,我只发布了clang代码,但g ++生成的代码结果相同,但不同代码块的顺序略有不同)