else语句,哪个更快?

有一天我和一位朋友争论了这两个片段。 哪个更快,为什么?

value = 5;
if (condition) {
    value = 6;
}

和:

if (condition) {
    value = 6;
} else {
    value = 5;
}

如果value是一个矩阵呢?

注意:我知道value = condition ? 6 : 5; value = condition ? 6 : 5; 存在,我预计它会更快,但它不是一种选择。

编辑 (由于问题暂时搁置,工作人员请求):

  • 请考虑由主流编译器(如g ++,clang ++,vc,mingw)在优化和非优化版本或MIPS汇编中生成的x86汇编。
  • 当汇编不同时,解释为什么版本更快,何时(例如“更好,因为没有分支和分支有问题blahblah”)

  • TL; DR:在未经优化的代码中, if没有else效率似乎无关紧要,但即使启用了最基本的优化级别,代码基本上也会被重写为value = condition + 5


    我试了一下,并为以下代码生成了程序集:

    int ifonly(bool condition, int value)
    {
        value = 5;
        if (condition) {
            value = 6;
        }
        return value;
    }
    
    int ifelse(bool condition, int value)
    {
        if (condition) {
            value = 6;
        } else {
            value = 5;
        }
        return value;
    }
    

    在禁用优化( -O0 )的gcc 6.3上,相关的区别是:

     mov     DWORD PTR [rbp-8], 5
     cmp     BYTE PTR [rbp-4], 0
     je      .L2
     mov     DWORD PTR [rbp-8], 6
    .L2:
     mov     eax, DWORD PTR [rbp-8]
    

    ifonly ,而ifelse

     cmp     BYTE PTR [rbp-4], 0
     je      .L5
     mov     DWORD PTR [rbp-8], 6
     jmp     .L6
    .L5:
     mov     DWORD PTR [rbp-8], 5
    .L6:
     mov     eax, DWORD PTR [rbp-8]
    

    后者看起来效率稍低,因为它有一个额外的跳跃,但都至少有两个和最多三个任务,所以除非你真的需要挤压每一滴表演(提示:除非你在航天飞机上工作,否则你不需要,即使这样你可能不会)差异不会引人注目。

    但是,即使在最低优化级别( -O1 )下,这两种功能也会降低到相同的水平:

    test    dil, dil
    setne   al
    movzx   eax, al
    add     eax, 5
    

    这基本上相当于

    return 5 + condition;
    

    假设condition是零或一个。 较高的优化级别不会真正改变输出,除非他们在movzx通过有效地清零EAX寄存器来避免movzx


    免责声明:你可能不应该自己写5 + condition (即使标准保证将true的类型转换为1 ),因为你的意图对读取你的代码的人(可能包括你未来的自己)来说可能不会立即显而易见。 这段代码的目的是为了说明编译器在两种情况下产生的结果(实际上)是相同的。 Ciprian Tomoiaga在评论中说得很好:

    一个的工作就是为人类编写代码,让编译器机器编写代码。


    CompuChip的答案显示,对于int它们都针对相同的程序集进行了优化,所以没关系。

    如果价值是一个矩阵呢?

    我将以更一般的方式来解释这个问题,即如果value是一种结构和任务昂贵(而且行动便宜)的类型。

    然后

    T value = init1;
    if (condition)
       value = init2;
    

    是次优的,因为在condition为真的情况condition ,您会对init1执行不必要的初始化,然后执行复制分配。

    T value;
    if (condition)
       value = init2;
    else
       value = init3;
    

    这个更好。 但是如果默认构造是昂贵的并且如果拷贝构造比初始化更昂贵,那么仍然不是最佳的。

    你有条件运营商的解决方案是很好的:

    T value = condition ? init1 : init2;
    

    或者,如果你不喜欢条件运算符,你可以像这样创建一个辅助函数:

    T create(bool condition)
    {
      if (condition)
         return {init1};
      else
         return {init2};
    }
    
    T value = create(condition);
    

    根据init1init2 ,你也可以考虑这个:

    auto final_init = condition ? init1 : init2;
    T value = final_init;
    

    但是我必须强调,只有在施工和作业对于给定类型而言非常昂贵的情况下,这才是相关的。 即使如此,只有通过分析你肯定知道。


    在伪汇编语言中,

        li    #0, r0
        test  r1
        beq   L1
        li    #1, r0
    L1:
    

    可能会或可能不会比...更快

        test  r1
        beq   L1
        li    #1, r0
        bra   L2
    L1:
        li    #0, r0
    L2:
    

    取决于实际CPU的复杂程度。 从最简单到最爱:

  • 对于大约1990年以后生产的任何CPU,良好的性能取决于指令高速缓存内的代码。 因此,如果有疑问,请尽量减少代码大小。 这有利于第一个例子。

  • 使用基本的“顺序五级流水线”CPU,这仍然是您在许多微控制器中所获得的大致结果,每当采用分支条件或无条件分支时都会产生管线泡沫,因此,尽量减少分支指令的数量。 这也有利于第一个例子。

  • 稍微复杂一点的CPU--足以做“无序执行”的想法,但不够花哨,无法使用该概念的最佳已知实现 - 可能会在遇到写入后写入危险时产生管道泡沫。 这有利于第二个例子,其中r0只写一次,无论如何。 这些CPU通常足以处理指令获取器中的无条件分支,因此您不仅仅为分支惩罚交易写后写惩罚。

    我不知道是否有人还在制造这种CPU。 但是,使用乱序执行的“最知名实现”的CPU很可能会偷偷使用不太常用的指令,所以您需要意识到这种事情可能会发生。 一个真实的例子是在Sandy Bridge CPU上的popcntlzcnt的目标寄存器上存在错误的数据依赖关系。

  • 在最高端,OOO引擎将发布完全相同的两个代码片段的内部操作序列 - 这是“不用担心它的硬件版本,编译器将以任何方式生成相同的机器代码”。 但是,代码大小仍然很重要,现在您还应该担心条件分支的可预测性。 分支预测失败可能会导致完整的管道刷新,这对性能来说是灾难性的; 请参阅为什么处理排序的数组比处理未排序的数组更快? 了解这可以造成多大的差异。

    如果分支高度不可预测,并且您的CPU具有条件设置或条件移动指令,则应该使用它们:

        li    #0, r0
        test  r1
        setne r0
    

    要么

        li    #0, r0
        li    #1, r2
        test  r1
        movne r2, r0
    

    条件集版本比任何其他替代方案都更紧凑; 如果该指令可用,那么即使该分支是可预测的,实际上也可以保证该方案是正确的。 条件移动版本需要额外的临时寄存器,并且总是浪费一条li指令的调度和执行资源; 如果分支实际上是可预测的,分支版本可能会更快。

  • 链接地址: http://www.djcxy.com/p/5381.html

    上一篇: else statement, which is faster?

    下一篇: Do sealed classes really offer performance Benefits?