我注意到,如果我们知道控制流很有可能是真是假,我们可以告诉编译器,例如,在Linux内核中,有很多likely
unlikely
,实际上是由gcc
提供的__builtin_expect
实现的,所以我想知道它是如何工作的,然后检查那里的程序集:
20:branch_prediction_victim.cpp **** if (array_aka[j] >= 128)
184 .loc 3 20 0 is_stmt 1
185 00f1 488B85D0 movq -131120(%rbp), %rax
185 FFFDFF
186 00f8 8B8485F0 movl -131088(%rbp,%rax,4), %eax
186 FFFDFF
187 00ff 83F87F cmpl $127, %eax
188 0102 7E17 jle .L13
然后对于__builtin_expect
20:branch_prediction_victim.cpp **** if (__builtin_expect((array_aka[j] >= 128), 1))
184 .loc 3 20 0 is_stmt 1
185 00f1 488B85D0 movq -131120(%rbp), %rax
185 FFFDFF
186 00f8 8B8485F0 movl -131088(%rbp,%rax,4), %eax
186 FFFDFF
187 00ff 83F87F cmpl $127, %eax
188 0102 0F9FC0 setg %al
189 0105 0FB6C0 movzbl %al, %eax
190 0108 4885C0 testq %rax, %rax
191 010b 7417 je .L13
- 188-
setg
设置如果大于,这里设置如果大于什么 - 189-
movzbl
移动零扩展字节到长,我知道这一次移动%al
到%eax
- 190-
testq
位OR然后设置ZF CF标志,对吗
我想知道它们是如何影响分支预测和提高性能的,还有三条额外的指令,需要更多的周期,对吗?
setcc
读取FLAGS,在本例中由cmp
之前设置。阅读手册。
这看起来像是忘记了启用优化,所以__builtin_expect
只是在寄存器中创建一个0
/1
布尔值,并对其进行非零分支,而不是对原始FLAGS条件进行分支不要看未优化的代码,它总是很糟糕的
线索是:作为likely
的一部分,脑死的booleaning,以及使用RBP作为movq -131120(%rbp), %rax
的帧指针从堆栈加载j
likely
通常不会改进运行时分支预测,它改进了代码布局,以在按照源代码所说的方式进行时(即快速情况(最大限度地减少分支数量。因此,对于常见情况,它改进了I-cache的局部性。例如,编译器会把事情安排好,所以常见的情况是一个未执行的条件分支,只是失败了。这使得超标量流水线CPU中的前端更容易同时获取/解码多条指令。继续在直线上提取是最容易的。
对于您知道是可预测的情况,likely
实际上可以让编译器使用分支而不是cmov
,即使编译器启发式(没有概要文件引导的优化(会出错。相关:gcc优化标志-O3使代码比-O2 慢