GCC&x27的优势是什么;在if-else语句中内置了s\uuu expect?
我遇到了一个GCC&x27的优势是什么;在if-else语句中内置了s\uuu expect?,c,linux,gcc,built-in,C,Linux,Gcc,Built In,我遇到了一个#define,他们在其中使用uu builtin\uu expect 说: 内置函数:long\u内置函数(long exp,long c) 您可以使用\u内置\u expect为编译器提供分支 预测信息。一般来说,您应该更喜欢使用实际值 与程序员一样,对此(-fprofile arcs)的配置文件反馈 众所周知,他们不善于预测程序的实际执行情况。 但是,有些应用程序很难收集这些数据 返回值是exp的值,它应该是一个整数 表情。内置函数的语义是 exp==c。例如: i
#define
,他们在其中使用uu builtin\uu expect
说:
内置函数:long\u内置函数(long exp,long c)
您可以使用\u内置\u expect
为编译器提供分支
预测信息。一般来说,您应该更喜欢使用实际值
与程序员一样,对此(-fprofile arcs
)的配置文件反馈
众所周知,他们不善于预测程序的实际执行情况。
但是,有些应用程序很难收集这些数据
返回值是exp
的值,它应该是一个整数
表情。内置函数的语义是
exp==c
。例如:
if (__builtin_expect (x, 0))
foo ();
表示我们不希望调用foo
,因为我们希望x
为零
那么为什么不直接使用:
if (x)
foo ();
正如描述中所说,第一个版本在构造中添加了一个预测元素,告诉编译器,
x==0
分支更可能是一个分支,也就是说,您的程序将更频繁地使用该分支
考虑到这一点,编译器可以优化条件,以便在满足预期条件的情况下,它所需的工作量最少,而在出现意外条件的情况下,可能需要做更多的工作
看看在编译阶段以及在生成的程序集中如何实现条件语句,看看一个分支如何比另一个分支少工作
然而,如果所讨论的条件是频繁调用的紧密内部循环的一部分,我只希望这种优化会产生显著的效果,因为结果代码中的差异相对较小。如果您以错误的方式对其进行优化,很可能会降低性能。内置式expect的思想是告诉编译器,您通常会发现表达式的计算结果为c,因此编译器可以针对这种情况进行优化 我猜有人认为他们很聪明,他们这样做是在加快速度 不幸的是,除非人们非常清楚这种情况(很可能他们没有做过这样的事情),否则情况很可能会变得更糟。文件甚至说: 一般来说,您应该更倾向于使用实际的概要文件反馈(
-fprofile arcs
),因为程序员在预测其程序实际执行情况方面是出了名的差。但是,有些应用程序很难收集这些数据
通常,您不应该使用\u内置\u expect
,除非:
- 您有一个非常真实的性能问题
- 您已经适当地优化了系统中的算法
- 您已经有了性能数据来支持您的断言,即某个特定情况最有可能发生
if (__builtin_expect(x, 0)) {
foo();
...
} else {
bar();
...
}
我想应该是这样的:
cmp $x, 0
jne _foo
_bar:
call bar
...
jmp after_if
_foo:
call foo
...
after_if:
您可以看到指令的排列顺序是,bar
大小写在foo
大小写之前(与C代码相反)。这可以更好地利用CPU管道,因为跳转会重击已经获取的指令
在执行跳转之前,跳转下方的指令(条形案例)被推送到管道中。由于
foo
的情况不太可能发生,跳跃也不太可能发生,因此不太可能破坏管道。我看不到任何解决我认为您所问问题的答案,请转述如下:
是否有一种更便于移植的方法向编译器提示分支预测
你问题的标题让我想到了这样做:
if ( !x ) {} else foo();
如果编译器假定更可能为“true”,则可以优化为不调用foo()
这里的问题只是,一般来说,您不知道编译器会假设什么——因此,任何使用这种技术的代码都需要仔细测量(如果上下文发生变化,可能会随着时间的推移进行监控)。让我们反编译,看看GCC4.8如何处理它 Blagovest提到了分支反转以改进管道,但当前的编译器真的做到了吗?让我们看看 不带
\u内置\u expect
#include "stdio.h"
#include "time.h"
int main() {
/* Use time to prevent it from being optimized away. */
int i = !time(NULL);
if (i)
puts("a");
return 0;
}
使用GCC 4.8.2 x86_64 Linux编译和反编译:
gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
输出:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 75 0a jne 1a <main+0x1a>
10: bf 00 00 00 00 mov $0x0,%edi
11: R_X86_64_32 .rodata.str1.1
15: e8 00 00 00 00 callq 1a <main+0x1a>
16: R_X86_64_PC32 puts-0x4
1a: 31 c0 xor %eax,%eax
1c: 48 83 c4 08 add $0x8,%rsp
20: c3 retq
我们得到:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 74 07 je 17 <main+0x17>
10: 31 c0 xor %eax,%eax
12: 48 83 c4 08 add $0x8,%rsp
16: c3 retq
17: bf 00 00 00 00 mov $0x0,%edi
18: R_X86_64_32 .rodata.str1.1
1c: e8 00 00 00 00 callq 21 <main+0x21>
1d: R_X86_64_PC32 puts-0x4
21: eb ed jmp 10 <main+0x10>
此优化不是使用-O0
完成的
但是祝您好运,编写一个使用\u内置比不使用运行得更快的示例。我天真的尝试
C++20[[可能]]
和[[不可能]]
#include "stdio.h"
#include "time.h"
int main() {
/* Use time to prevent it from being optimized away. */
int i = !time(NULL);
if (i)
puts("a");
return 0;
}
C++ 20已经对C++内置的内容进行了标准化:它们可能会(双关)做同样的事情。
< P>我根据Mac BraoReaveBuykLiEv和@西罗在MAC上测试。集合看起来很清晰,我添加了注释
命令是
gcc-c-O3-std=gnu11 testOpt.c;otool-tVI测试选项
当我使用-O3时,无论内置的expect(I,0)是否存在,它看起来都是一样的
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp // open function stack
0000000000000004 xorl %edi, %edi // set time args 0 (NULL)
0000000000000006 callq _time // call time(NULL)
000000000000000b testq %rax, %rax // check time(NULL) result
000000000000000e je 0x14 // jump 0x14 if testq result = 0, namely jump to puts
0000000000000010 xorl %eax, %eax // return 0 , return appear first
0000000000000012 popq %rbp // return 0
0000000000000013 retq // return 0
0000000000000014 leaq 0x9(%rip), %rdi ## literal pool for: "a" // puts part, afterwards
000000000000001b callq _puts
0000000000000020 xorl %eax, %eax
0000000000000022 popq %rbp
0000000000000023 retq
当使用-O2编译时,使用和不使用内置的expect(i,0)时,它看起来不同
首先没有
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 xorl %edi, %edi
0000000000000006 callq _time
000000000000000b testq %rax, %rax
000000000000000e jne 0x1c // jump to 0x1c if not zero, then return
0000000000000010 leaq 0x9(%rip), %rdi ## literal pool for: "a" // put part appear first , following jne 0x1c
0000000000000017 callq _puts
000000000000001c xorl %eax, %eax // return part appear afterwards
000000000000001e popq %rbp
000000000000001f retq
现在使用内置的expect(i,0)
总而言之,内置的expect在最后一种情况下有效。在大多数情况下,您应该保持分支预测不变,不必担心它
其中一个有益的例子是CPU密集型算法,它有很多分支。在某些情况下,跳转可能导致超出当前CPU程序缓存,使CPU等待软件的下一部分运行。通过将不太可能出现的分支推到最后,你将保留你的记忆
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 xorl %edi, %edi
0000000000000006 callq _time
000000000000000b testq %rax, %rax
000000000000000e jne 0x1c // jump to 0x1c if not zero, then return
0000000000000010 leaq 0x9(%rip), %rdi ## literal pool for: "a" // put part appear first , following jne 0x1c
0000000000000017 callq _puts
000000000000001c xorl %eax, %eax // return part appear afterwards
000000000000001e popq %rbp
000000000000001f retq
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 xorl %edi, %edi
0000000000000006 callq _time
000000000000000b testq %rax, %rax
000000000000000e je 0x14 // jump to 0x14 if zero then put. otherwise return
0000000000000010 xorl %eax, %eax // return appear first
0000000000000012 popq %rbp
0000000000000013 retq
0000000000000014 leaq 0x7(%rip), %rdi ## literal pool for: "a"
000000000000001b callq _puts
0000000000000020 jmp 0x10