几年前,我正在学习x86汇编程序,CPU流水线,缓存未命中,分支预测以及所有爵士乐.
这是两个半的故事.我在处理器viz指令重新排序,缓存预加载,依赖性交错等方面阅读了冗长流水线的所有优点.
缺点是规范的任何偏差都是非常昂贵的.例如,IIRC在早期千兆赫时代的某个AMD处理器每次通过指针(!)调用函数时都会有40个周期的惩罚,这显然是正常的.
这不是一个可以忽略不计的"别担心"号码!请记住,"良好设计"通常意味着"尽可能地考虑您的功能"和"在数据类型中编码语义",这通常意味着虚拟接口.
权衡是不执行此类操作的代码每个周期可能获得两个以上的指令.这些是在编写高性能C++代码时需要担心的数字,这些代码在对象设计上很重要并且可以解决数字运算问题.
据我所知,随着我们进入低功耗时代,长CPU流水线趋势已经逆转.这是我的问题:
最新一代x86兼容处理器是否仍然遭受虚拟功能调用,坏分支预测等的严重处罚?
编辑:我的混乱是因为肯定通过预测采取了哪个分支,你是否也在有效地进行目标预测?
这个问题与我关于这个主题的第一个问题有着内在的联系:
看看接受的答案:
无条件分支,固定目标
- 无限循环
goto声明break或continue声明if/else声明的'then'子句结束(跳过该else子句)- 非虚函数调用
无条件分支,变量目标
- 从函数返回
- 虚函数调用
- 函数指针调用
switch语句(如果编译成跳转表)条件分支,固定目标
if声明switch声明(如果汇编成一系列if/else声明)- 循环条件测试
- 在
&&与||运营商- 三元
?:运算符条件分支,变量目标
- 在正常情况下不太可能出现,但编译器可能会综合一个作为优化,结合上述两种情况.例如,在x86上,编译器可以将代码优化
if (condition) { obj->VirtualFunctionCall(); }为条件间接跳转,就像jne *%eax它由于尾调用优化而出现在函数末尾一样.
如果我有以下代码:
if(something){
//a
}
else{
//b
}
Run Code Online (Sandbox Code Playgroud)
(BP ="分支预测"和BTP ="分支目标预测")
其非常明显的BP用于评估条件something.但是,我试图了解BTP是否也参与确定分支中发生的情况a.BTP是否也恰好确定位于branch a/ 的代码的地址b,具体取决于BP的结果?
我问这个维基百科页面(http://en.wikipedia.org/wiki/Branch_target_predictor):
在计算机体系结构中,分支目标预测器是处理器的一部分,其在由处理器的执行单元计算分支指令的目标之前预测所采用的条件分支或无条件分支指令的目标.
它表明BTP用于在预测条件后预测目标.
1)有人可以澄清以上内容吗?
第二个相关问题 - BP和BTP如何与CPU的fetch/decode/execute/write-back管道交互方式不同?BP是从获取还是解码阶段开始的?在条件代码的执行阶段之后,我们可以检查预测是否正确并更新分支预测缓存.
2)BTP如何处理fetch/decode/execute/write-back …
从这里我知道英特尔近年来实施了几种静态分支预测机制:
80486年龄:永远不被采取
Pentium4年龄:未采取后退/前锋
像Ivy Bridge,Haswell这样的新型CPU变得越来越无形,请参阅Matt G的实验.
英特尔似乎不想再谈论它,因为我在英特尔文档中找到的最新资料大约是十年前写的.
我知道静态分支预测(远远不是)比动态更重要,但在很多情况下,CPU将完全丢失,程序员(使用编译器)通常是最好的指南.当然,这些情况通常不是性能瓶颈,因为一旦频繁执行分支,动态预测器就会捕获它.
由于英特尔不再在其文档中明确声明动态预测机制,因此GCC的builtin_expect()只能从热路径中删除不太可能的分支.
我不熟悉CPU的设计,我不知道究竟是什么机制,目前英特尔使用其静态预测,但我还是觉得英特尔的最佳机制应该清楚地记录他的CPU",我打算去当动态预测失败,向前或向后',因为通常程序员是当时最好的指南.
更新:
我发现你提到的主题逐渐超出我的知识范围.这里涉及一些动态预测机制和CPU内部细节,我在两三天内无法学习.所以请允许我暂时退出你的讨论并充电.
这里仍然欢迎任何答案,也许会帮助更多人
compiler-construction x86 intel cpu-architecture branch-prediction
作为我的问题的后续问题在x86-64中使用32位寄存器/指令的优点,我开始测量指令的成本.我知道这已经多次完成了(例如Agner Fog),但我这样做是为了娱乐和自我教育.
我的测试代码非常简单(为简单起见,这里是伪代码,实际上是汇编程序):
for(outer_loop=0; outer_loop<NO;outer_loop++){
operation #first
operation #second
...
operation #NI-th
}
Run Code Online (Sandbox Code Playgroud)
但是应该考虑一些事情.
NI>10^7),则循环的整个内容不适合指令高速缓存,因此必须一遍又一遍地加载,使得RAM的速度定义执行所需的时间.例如,对于大的内部部分,xorl %eax, %eax(2个字节)比xorq %rax, %rax(3个字节)快33%.NI是小,整个循环可轻松放入指令缓存,比xorl %eax, %eax和xorq %rax, %rax同样快速,可以执行每时钟周期的4倍.然而,这个简单的模型并没有为jmp建筑提供水.对于jmp-instruction,我的测试代码如下所示:
for(outer_loop=0; outer_loop<NO;outer_loop++){
jmp .L0
.L0: jmp .L1
L1: jmp L2
....
}
Run Code Online (Sandbox Code Playgroud)
结果是:
NI>10^4),我测量4.2 ns/ - jmp指令(相当于从RAM加载的42个字节或在我的机器上大约12个时钟周期).NI<10^3),我测量1 ns/jmp-指令(大约3个时钟周期,听起来似乎合理--Agner Fog的表显示了2个时钟周期的成本).该指令jmp LX使用2字节eb 00 …
我试图详细了解当分支预测错误时,skylake CPU管道的各个阶段中的指令会发生什么,以及从正确的分支目标开始执行指令的速度如何。
因此,让我们在这里将两个代码路径标记为红色(一个预测但未实际采用)和绿色(一个已预测但未预期)。所以问题是:1.在红色指令开始被丢弃之前,分支必须经过管道多远(以及在管道的哪个阶段被丢弃)?2.绿色指令(在分支到达的流水线阶段方面)多久可以开始执行?
我看过Agner Fogg的文档和许多讲义,但这些观点并不清楚。
x86 intel cpu-architecture speculative-execution branch-prediction