三元组还是 if 语句更快?

Are ternaries or if statements faster?­

所以我有两个选择,两个函数的类型相同:

(Entry->d_type == DT_DIR ? rmdirr : remove)(CurrentEntryPath);

if (Entry->d_type == DT_DIR) {
    rmdirr(CurrentEntryPath);
} else {
    remove(CurrentEntryPath);
}

我已经确认三进制是 %100% 安全的,因为这两个函数都是兼容的指针类型。哪个更快(即使可读性较差)?

很难判断什么实际上更有效率。 if-else 产生的指令较少,但如果不满足分支预测,则有一条分支指令需要流水线刷新。

#define SOMEVALUE 5


int __attribute__((noinline)) foo(int x)
{
    return rand();
}

int __attribute__((noinline)) boo(int x)
{
    return rand();
}


int aaa(int x)
{
    int result;

    if(x == 5) 
        result = foo(x);
    else
        result = boo(x);

    return result;
}

int bbb(int x)
{
    int result;

    return (x == 5 ? foo : boo)(x);
}

int (*z[2])(int) = {foo, boo};

int ccc(int x)
{
    return z[!!(x == 5)](x);
}

以及生成的代码:

foo:
        jmp     rand
boo:
        jmp     rand
aaa:
        cmp     edi, 5
        je      .L6
        jmp     boo
.L6:
        jmp     foo
bbb:
        cmp     edi, 5
        mov     eax, OFFSET FLAT:foo
        mov     edx, OFFSET FLAT:boo
        cmovne  rax, rdx
        jmp     rax
ccc:
        xor     eax, eax
        cmp     edi, 5
        sete    al
        jmp     [QWORD PTR z[0+rax*8]]
z:
        .quad   foo
        .quad   boo

https://godbolt.org/z/L6CFs9

在我看来,如果您在不那么琐碎的代码中进行这样的微优化 - 您需要查看生成的代码并决定什么更有效。

很可能优化编译器会生成相同的 两种情况的代码。

奇怪的是 gcc 和 clang 在这种情况下不这样做而是生成 :? 情况下字面上使用函数指针的代码 第二种情况直接跳转。

示例:

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>

int rmitem0(struct dirent const*Entry)
{
    return (Entry->d_type == DT_DIR ? rmdir : remove)(Entry->d_name);
}

int rmitem1(struct dirent const*Entry)
{
    if (Entry->d_type == DT_DIR)
        return rmdir(Entry->d_name);
    else return remove(Entry->d_name);
}

x86_64 铿锵声:

0000000000000000 <rmitem0>:
   0:   80 7f 12 04             cmp    BYTE PTR [rdi+0x12],0x4
   4:   b8 00 00 00 00          mov    eax,0x0  5: R_X86_64_32  rmdir
   9:   b9 00 00 00 00          mov    ecx,0x0  a: R_X86_64_32  remove
   e:   48 0f 44 c8             cmove  rcx,rax
  12:   48 83 c7 13             add    rdi,0x13
  16:   ff e1                   jmp    rcx

0000000000000018 <rmitem1>:
  18:   80 7f 12 04             cmp    BYTE PTR [rdi+0x12],0x4
  1c:   48 8d 7f 13             lea    rdi,[rdi+0x13]
  20:   0f 85 00 00 00 00       jne    26 <rmitem1+0xe> 22: R_X86_64_PLT32  remove-0x4
  26:   e9 00 00 00 00          jmp    2b <rmitem1+0x13>    27: R_X86_64_PLT32  rmdir-0x4

x86_64 gcc:

0000000000000000 <rmitem0>:
   0:   80 7f 12 04             cmp    BYTE PTR [rdi+0x12],0x4
   4:   74 09                   je     f <rmitem0+0xf>
   6:   48 8b 05 00 00 00 00    mov    rax,QWORD PTR [rip+0x0]        # d <rmitem0+0xd> 9: R_X86_64_REX_GOTPCRELX   remove-0x4
   d:   eb 07                   jmp    16 <rmitem0+0x16>
   f:   48 8b 05 00 00 00 00    mov    rax,QWORD PTR [rip+0x0]        # 16 <rmitem0+0x16>   12: R_X86_64_REX_GOTPCRELX  rmdir-0x4
  16:   48 83 c7 13             add    rdi,0x13
  1a:   ff e0                   jmp    rax

000000000000001c <rmitem1>:
  1c:   4c 8d 47 13             lea    r8,[rdi+0x13]
  20:   80 7f 12 04             cmp    BYTE PTR [rdi+0x12],0x4
  24:   4c 89 c7                mov    rdi,r8
  27:   75 05                   jne    2e <rmitem1+0x12>
  29:   e9 00 00 00 00          jmp    2e <rmitem1+0x12>    2a: R_X86_64_PLT32  rmdir-0x4
  2e:   e9 00 00 00 00          jmp    33 <rmitem1+0x17>    2f: R_X86_64_PLT32  remove-0x4

因此,这两种策略在这里的性能特征应该略有不同,但无论如何,你只见一棵小树,不见森林。

我在 Linux 上测得 rmdir 的持续时间约为 14µs。

上面的条件应该只需要一小部分 ns,最多几 ns:这比你的瓶颈快 10,000 多倍。

规则 #0 - 不要考虑原始速度;相反,请考虑 "which would I rather fix 8 months from now when someone reports a bug".

规则 #1 - 衡量,不要猜测,也不要让无法访问您的系统的人猜测。在目标系统上编写两个版本并对它们进行分析 - 检查生成的机器代码,并且 运行 每个版本都针对足够大的测试集以生成可用的统计信息并分析结果。考虑一下它是如何使用的——它是在一个紧密的循环中被调用数千次,还是在程序的整个生命周期中被调用一次?每个函数都涉及更新文件系统,与决定调用哪个函数相比,这将花费更多数量级的时间来执行 无论您使用哪种方法

规则 #2 - 如果你的代码给了你错误的答案,或者做了错误的事情,或者将你的信用卡信息暴露给了世界,或者如果有人在隔壁房间打喷嚏,或者没有人(包括您自己)可以修复或更新它。代码的正确性首先,其次是可读性和可维护性,其次是安全性和可靠性,然后是速度。大部分显着的速度提升来自使用正确的算法和数据结构,而不是您选择的流量控制结构。

规则 #3 - 不要使用三元运算符代替 if-else 结构 只是 进行流量控制;那不是它的工作。虽然第一个版本有效,但它有点刺眼且难以一目了然,当你从现在起六个月后重新拿起它时,你会问自己为什么这样做。而且我几乎可以保证它不会比其他方法更快或更慢。

我并不是说速度不重要 - 我是说速度只是需要考虑的一件事,除非您在特定领域工作,否则它不是最重要的事情。