在最新的英特尔软件开发手册中,它描述了两个操作码前缀:
Group 2 > Branch Hints
0x2E: Branch Not Taken
0x3E: Branch Taken
这些允许跳转指令的显式分支预测(操作码如Jxx
)
我记得几年前读到过,在x86上,显式分支预测本质上是在gccs分支前置固函数的上下文中的no-op。
我现在不清楚这些x86分支提示是否是一个新功能,或者它们在实践中本质上是无操作的。
任何人都可以清除它吗?
(也就是说:gccs 分支预测函数是否会生成这些 x86 分支提示? - 当前的英特尔 CPU 不会忽略它们吗? - 这是什么时候发生的?
更新:
我创建了一个快速测试程序:
int main(int argc, char** argv)
{
if (__builtin_expect(argc,0))
return 1;
if (__builtin_expect(argc == 2, 1))
return 2;
return 3;
}
拆卸成以下部件:
00000000004004cc <main>:
4004cc: 55 push %rbp
4004cd: 48 89 e5 mov %rsp,%rbp
4004d0: 89 7d fc mov %edi,-0x4(%rbp)
4004d3: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4004d7: 8b 45 fc mov -0x4(%rbp),%eax
4004da: 48 98 cltq
4004dc: 48 85 c0 test %rax,%rax
4004df: 74 07 je 4004e8 <main+0x1c>
4004e1: b8 01 00 00 00 mov $0x1,%eax
4004e6: eb 1b jmp 400503 <main+0x37>
4004e8: 83 7d fc 02 cmpl $0x2,-0x4(%rbp)
4004ec: 0f 94 c0 sete %al
4004ef: 0f b6 c0 movzbl %al,%eax
4004f2: 48 85 c0 test %rax,%rax
4004f5: 74 07 je 4004fe <main+0x32>
4004f7: b8 02 00 00 00 mov $0x2,%eax
4004fc: eb 05 jmp 400503 <main+0x37>
4004fe: b8 03 00 00 00 mov $0x3,%eax
400503: 5d pop %rbp
400504: c3 retq
400505: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40050c: 00 00 00
40050f: 90 nop
我看不到2E或3E?也许gcc出于某种原因忽略了它们?
虽然奔腾4是唯一一代真正尊重分支提示指令的处理器,但大多数处理器确实有某种形式的静态分支预测,可以用来达到同样的效果。这个答案与最初的问题有点相干,但我认为这对任何来到这个页面的人来说都是有价值的信息。
英特尔优化指南和Agner Fog的指南(这里已经提到过)都对此功能进行了出色的描述。
英特尔对比酷睿2更新的几代有这样的说法:
使条件分支后面的直通代码成为具有前向目标的分支的可能目标
因此,静态预测算法预测代码中向前跳转的条件分支不会被采用。
这与GCC似乎使用__builtin_expect
生成的内容一致:“预期的”返回1
/返回2
代码被放置在条件分支的未采取路径中,这些路径将被静态预测为未采取。
此外:
使用静态预测算法预测分支目标缓冲区中没有历史记录的分支:
>
预测要采取的无条件分支。
预测不采取的间接分支。
因此,在GCC将无条件jmp
s放置到函数末尾的“预期”未执行路径中,这些跳转将被静态预测为已执行(即未跳过)。
英特尔还表示:
使条件分支之后的失败代码成为具有向后目标的分支的不可能目标
因此,通过静态预测算法预测在代码中向后跳转的条件分支被预测为被采用。
根据Agner Fog的说法,大多数奔腾也遵循以下算法:
在PPro、P2、P3、P4和P4E上,以前从未见过或不在“分支目标缓冲区”中的控制传输指令,如果向前执行,预计会失败,如果向后执行,则会被执行(例如循环)。在这些处理器上,静态预测比动态预测需要更长的时间。
然而,Core 2系列(和奔腾M)有一个完全不同的政策:
这些处理器不使用静态预测。预测器只是在第一次看到分支时进行随机预测,这取决于分配给新分支的BTB条目中发生了什么。做出正确的跳跃或不跳跃预测的可能性只有50%,但预测的目标是正确的。
AMD处理器显然也是如此:
预测分支在第一次被看到时不会被带走。预测分支总是在第一次被占用之后被占用。动态预测仅在分支被占用后才使用。分支提示前缀不起作用。
还有一个需要考虑的因素:CPU通常喜欢以线性方式执行,因此即使正确预测的分支也比正确预测的未获取分支更昂贵。
gcc不生成前缀是正确的,因为它们对奔腾4以后的所有处理器都没有影响。
但是__builtin_expect
还有其他效果,例如将不期望的代码路径从代码中的缓存热位置移开或内联决策,因此它仍然很有用。
这些指令前缀对现代处理器(任何比奔腾4更新的处理器)都没有影响。它们只需要占用一字节的代码空间,因此,不生成它们是正确的做法。
有关详细信息,请参阅Agner Fog的优化手册,特别是3.微架构:http://www.agner.org/optimize/
英特尔®64和IA-32架构优化参考手册在关于优化分支的部分(第3.4.1节)中不再提及它们:http://www.intel.de/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf
这些前缀是网爆架构的(无害的)遗物。在全面优化中,您可以使用它们来对齐代码,但这就是它们现在的全部优点。
如果语句更多地依赖于分支预测,而v表查找更多地依赖分支目标预测,那么
分支目标预测(BTP)与分支预测(BP)不同。我知道BTP会找到分支将跳转到的位置,而BP只是决定可能采取哪个分支。 BTP依赖BP吗,如果BTP不使用BP来预测哪个分支被采用,它怎么可能知道分支的目标呢? 我不明白为什么会有这么大的差异?一旦分支被预测为被占用,找到目标并不像读取指令中的地址一样简单吗?
建立一个 CDN,你能配置assetPrefix选项,去配置你的 CDN 源。 const isProd = process.env.NODE_ENV === 'production' module.exports = { // You may only need to add assetPrefix in the production. assetPrefix: isProd ? 'ht
我目前正在编写一个Intel 8042驱动程序,并编写了两个循环来等待一些缓冲区准备就绪: 如您所见,我在循环中插入了指令。我最近才知道它,很自然地想尝试一下。 由于 的内容是不可预测的,因为它是 I/O 读取,因此分支预测器将使用循环指令填充管道:经过一些迭代后,它会注意到总是采用一个分支,类似于这里的情况。 如果分支预测器真的在其预测中包含I/O指令,那么上述是正确的,我不确定。 那么分支预测
编辑:我的困惑出现了,因为通过预测哪个分支,你肯定也在有效地进行目标预测?? 这个问题与我关于这个主题的第一个问题有内在联系: 分支预测与分支目标预测 无限循环 语句 或语句 语句的“then”子句结尾(跳过子句) 非虚函数调用 从函数返回 虚函数调用 函数指针调用 语句(如果编译为跳转表) 语句 语句(如果编译成一系列语句) 循环条件测试 和运算符 三元运算符 null 如果我有以下代码: (B
我的代码经常调用具有多个(不可预测的)分支的函数。当我分析时,我发现这是一个小瓶颈,大部分CPU时间用于条件JMP。 考虑以下两个函数,其中原始函数有多个显式分支。 这是一个新函数,我试图在其中删除导致瓶颈的分支。 然而,当我分析新代码时,性能只提高了大约20%,而且调用本身(对mem_funcs数组中的一个func)花费了很长时间。 第二个变量仅仅是一个更隐含的条件吗,因为CPU仍然无法预测将要