所以我有两个选择,两个函数的类型都一样。
(Entry->d_type == DT_DIR ? rmdirr : remove)(CurrentEntryPath);
Or
if (Entry->d_type == DT_DIR) {
rmdirr(CurrentEntryPath);
} else {
remove(CurrentEntryPath);
}
我已经确认三元函数是百分之百安全的,因为两个函数都是兼容的指针类型。哪一个更快(即使可读性更低)?
很难判断哪种方式实际上更有效率。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
在我看来,如果你在不那么琐碎的代码中做这样的微优化--你需要看到产生的代码,并决定什么是更有效的。
规则#0--不要从原始速度的角度考虑,而是要从 "8个月后当有人报告一个bug时,我宁愿修复哪个 "的角度考虑。
规则#1 - 衡量,不要猜测,也不要让没有权限进入你系统的人去猜测。 在目标系统上编写两个版本的代码,并对它们进行剖析--检查生成的机器代码,并将每个版本与一个足够大的测试集进行对比,以生成可用的统计数据并分析结果。 考虑它是如何使用的--它是在一个紧密的循环中被调用数千次,还是在程序的生命周期中被调用一次? 每个函数都涉及到更新文件系统,这比决定调用哪个函数所花费的时间要多很多数量级 不管 的方法。
规则2--如果你的代码给了你错误的答案,或者做了错误的事情,或者把你的信用卡信息暴露给了全世界,或者如果隔壁房间的人打了个喷嚏就会爆炸,或者没有人(包括你自己)能修复或更新它,那么你的代码有多快都不重要。 正确性代码 第一首先是可读性和可维护性,然后是安全性和可靠性。然后 的速度。 你的大部分显著的速度提升来自于使用正确的算法和数据结构,而不是你对流控结构的选择。
规则#3 - 不要用三元运算符来替代 if-else
架构 只是 的流量控制;这不是它的工作。 虽然第一个版本是有效的,但它有点刺眼,而且很难一目了然,当你六个月后再拿起它时,你会问自己为什么要这么做。 而且我几乎可以保证,它不会比另一种方法快或慢得可以衡量。
我不是说速度不重要--我是说速度只是需要考虑的一件事,除非你在特定的领域工作,否则它不是最重要的事情。
非常可以想象,一个优化的编译器会针对两种情况生成samecode。
奇怪的是,在这种情况下,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 clang:
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
因此,这两种策略在这里应该会有稍微不同的性能特征,但无论如何,你都错过了一棵小树的森林。
我已经测量了一个 rmdir
在Linux上约为14µs。
上面的条件应该只需要一个ns的一小部分,最多几个ns:这比你的瓶颈快了10000多倍。