C 三元表或if语句是否更快

C 三元表或if语句是否更快,c,performance,function,if-statement,conditional-operator,C,Performance,Function,If Statement,Conditional Operator,所以我有两个选项,两个函数都有相同的类型: (Entry->d_type == DT_DIR ? rmdirr : remove)(CurrentEntryPath); 或 我已经确认三元是100%安全的,因为这两个函数都是兼容的指针类型。哪一个速度更快,即使可读性较低?很难判断哪一个更有效。if-else生成的指令较少,但如果不满足分支预测,则存在需要管道刷新的分支指令 #define SOMEVALUE 5 int __attribute__((noinline)) foo(in

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

(Entry->d_type == DT_DIR ? rmdirr : 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

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

很有可能一个优化编译器会生成同样的结果 这两种情况的代码

奇怪的是,在本例中,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通用条款:

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:比瓶颈快10000倍以上。

规则0-不要考虑原始速度;相反,想想从现在起8个月后,当有人报告一个bug时,我更愿意修复哪个方面

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

规则2——如果你的代码给出了错误的答案,或者做了错误的事情,或者将你的信用卡信息泄露给了全世界,或者如果隔壁房间有人打喷嚏,或者包括你自己在内的任何人都无法修复或更新,那么代码的速度有多快都无关紧要。代码首先是正确性,然后是可读性和可维护性,然后是安全性和可靠性,然后是速度。大多数显著的速度提升来自于使用正确的算法和数据结构,而不是选择流量控制结构

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


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

任何合理的现代编译器都会为这些尝试godbolt创建完全相同的代码,以查看创建的内容。我没有看到任何编译器或标志的组合为这些godbolt生成完全相同的代码。与系统调用相比,任何性能差异都可能很小。我认为if更具可读性。@MooingDuck:在removeCurrentEntryPath中,remove根据C 2018 6.3.2.1 4转换为函数指针:除非是sizeof运算符的操作数或一元&运算符,否则类型为function returning type的函数指示符将转换为类型指针指向function returning type的表达式当然,这也适用于其他函数调用。这并不奇怪,因为它们的定义不同。三元运算符生成值,然后使用该值。如果满足条件,if-else只执行代码块。我的godbolt测试生成了相同的代码。但更简单的代码是。它避免了分支和分支错误预测在某些情况下,使用三元组而不是if/else的较大代码的总体性能会更好。对于一个或另一个备选方案的单独执行,分析是不同的。此外,三元组可能有其他影响,例如阻止编译器内联一个或两个函数。我认为这个答案的这句话对于比较问题中给出的代码的单独评估是完全正确的。
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