我目前正在查看CPU管道的各个部分,它们可以检测分支错误预测.我发现这些是:
我知道2和3检测到了什么,但我不明白在BTB中检测到了什么错误预测.BAC检测BTB错误地预测非分支指令的分支的位置,其中BTB未能检测到分支,或者BTB错误预测了x86 RET指令的目标地址.执行单元评估分支并确定它是否正确.
在分支目标缓冲区中检测到什么类型的错误预测?究竟在这里发现了什么错误预测?
我能找到的唯一线索是英特尔开发者手册第3卷(底部的两个BPU CLEAR事件计数器):

BPU在错误地认为未采取分支后预测了一个分支.
这似乎暗示预测并非"同步",而是"异步",因此"在错误地假设"之后?
更新:
Ross,这是CPU分支电路,来自最初的英特尔专利(如何用于"阅读"?):

我在任何地方都看不到"分支预测单位"?读过这篇论文的人会认为"BPU"是将BTB电路,BTB缓存,BAC和RSB分组在一起的懒惰方式吗?
所以我的问题仍然存在,哪个组件会引发BPU CLEAR信号?
optimization intel cpu-architecture computer-architecture branch-prediction
从这里我知道英特尔近年来实施了几种静态分支预测机制:
80486年龄:永远不被采取
Pentium4年龄:未采取后退/前锋
像Ivy Bridge,Haswell这样的新型CPU变得越来越无形,请参阅Matt G的实验.
英特尔似乎不想再谈论它,因为我在英特尔文档中找到的最新资料大约是十年前写的.
我知道静态分支预测(远远不是)比动态更重要,但在很多情况下,CPU将完全丢失,程序员(使用编译器)通常是最好的指南.当然,这些情况通常不是性能瓶颈,因为一旦频繁执行分支,动态预测器就会捕获它.
由于英特尔不再在其文档中明确声明动态预测机制,因此GCC的builtin_expect()只能从热路径中删除不太可能的分支.
我不熟悉CPU的设计,我不知道究竟是什么机制,目前英特尔使用其静态预测,但我还是觉得英特尔的最佳机制应该清楚地记录他的CPU",我打算去当动态预测失败,向前或向后',因为通常程序员是当时最好的指南.
更新:
我发现你提到的主题逐渐超出我的知识范围.这里涉及一些动态预测机制和CPU内部细节,我在两三天内无法学习.所以请允许我暂时退出你的讨论并充电.
这里仍然欢迎任何答案,也许会帮助更多人
compiler-construction x86 intel cpu-architecture branch-prediction
什么是callx86机器代码中绝对指针的"正确"方法?有没有一种方法可以在一条指令中完成它?
我想做什么:
我正在尝试基于"子程序线程"构建一种简化的迷你JIT(仍然).它基本上是字节码解释器中最短的步骤:每个操作码都是作为一个单独的函数实现的,因此每个基本的字节码块都可以"JIT"到它自己的新程序中,如下所示:
{prologue}
call {opcode procedure 1}
call {opcode procedure 2}
call {opcode procedure 3}
...etc
{epilogue}
Run Code Online (Sandbox Code Playgroud)
因此,我们的想法是每个块的实际机器代码只能从模板中粘贴(根据需要扩展中间部分),并且需要"动态"处理的唯一位是将每个操作码的函数指针复制到正确的位置作为每个调用指令的一部分.
我遇到的问题是了解call ...模板部分的用途.x86似乎没有考虑到这种用法,并且有利于相对和间接调用.
它看起来像我可以使用FF 15 EFBEADDE或2E FF 15 EFBEADDE在假设调用函数DEADBEEF(通过把东西变成一个汇编和反汇编,看到什么产生有效的结果,基本上发现了这些未通过了解他们在做什么),但我不理解的东东细分,特权和相关信息足以看出差异,或者这些信息与更常见的call指令有何不同.英特尔架构手册还建议这些仅在32位模式下有效,在64位模式下"无效".
有人可以解释这些操作码以及我是如何或者是否会为此目的使用它们或其他人?
(通过寄存器使用间接调用也有明显的答案,但这似乎是"错误的"方法 - 假设实际存在直接调用指令.)
有时gcc使用32位寄存器,当我希望它使用64位寄存器时.例如以下C代码:
unsigned long long
div(unsigned long long a, unsigned long long b){
return a/b;
}
Run Code Online (Sandbox Code Playgroud)
使用-O2选项编译(省略一些样板文件):
div:
movq %rdi, %rax
xorl %edx, %edx
divq %rsi
ret
Run Code Online (Sandbox Code Playgroud)
对于无符号除法,寄存器%rdx需要0.这可以通过xorq %rdx, %rdx但xorl %edx, %edx似乎具有相同的效果来实现.
至少在我的机器上没有性能提升(即加速)进行xorl了xorq.
我实际上不只是一个问题:
xorl并且不使用xorw?xorl比这更快的机器xorq?像Kaby Lake这样的现代CPU如何处理小分支?(在下面的代码中是跳转到标签LBB1_67).据我所知,分支不会有害,因为跳转不如16字节块大小,这是解码窗口的大小.
或者是否有可能由于某些宏观操作融合,分支将被完全省略?
sbb rdx, qword ptr [rbx - 8]
setb r8b
setl r9b
mov rdi, qword ptr [rbx]
mov rsi, qword ptr [rbx + 8]
vmovdqu xmm0, xmmword ptr [rbx + 16]
cmp cl, 18
je .LBB1_67
mov r9d, r8d
.LBB1_67: # in Loop: Header=BB1_63 Depth=1
vpcmpeqb xmm0, xmm0, xmmword ptr [rbx - 16]
vpmovmskb ecx, xmm0
cmp ecx, 65535
sete cl
cmp rdi, qword ptr [rbx - 32]
sbb rsi, qword ptr [rbx - 24]
setb dl
and …Run Code Online (Sandbox Code Playgroud) 我正在尝试从机器代码调用一个函数 - 在编译和链接时应该有一个绝对地址。我正在创建一个指向所需函数的函数指针,并试图将其传递给 call 指令,但我注意到 call 指令最多占用 16 位或 32 位地址。有没有办法调用绝对 64 位地址?
我正在部署 x86-64 架构并使用 NASM 生成机器代码。
如果我可以保证可执行文件肯定会映射到内存的底部 4GB,我可以使用 32 位地址,但我不确定在哪里可以找到该信息。
编辑:我不能使用 callf 指令,因为这需要我禁用 64 位模式。
第二次编辑:我也不想将地址存储在寄存器中并调用寄存器,因为这对性能至关重要,而且我无法承受间接函数调用的开销和性能影响。
最终编辑:通过确保我的机器代码映射到前 2GB 内存,我能够使用 rel32 调用指令。这是通过带有 MAP_32BIT 标志的 mmap 实现的(我使用的是 linux):
MAP_32BIT (自 Linux 2.4.20, 2.6) 将映射放入进程地址空间的前 2 GB。对于 64 位程序,此标志仅在 x86-64 上受支持。添加它是为了允许在前 2GB 内存中的某处分配线程堆栈,以提高某些早期 64 位处理器上的上下文切换性能。现代 x86-64 处理器不再具有此功能?形式问题,因此在这些系统上不需要使用此标志。设置 MAP_FIXED 时,将忽略 MAP_32BIT 标志。
我正在尝试使用我的 Intel i7-10700 和 ubuntu 20.04 来验证两个可熔断对可以在同一时钟周期内解码的结论。
测试代码排列如下,复制了8000次左右,以避免LSD和DSB的影响(主要使用MITE)。
ALIGN 32
.loop_1:
dec ecx
jge .loop_2
.loop_2:
dec ecx
jge .loop_3
.loop_3:
dec ecx
jge .loop_4
.loop_4:
.loop_5:
dec ecx
jge .loop_6
Run Code Online (Sandbox Code Playgroud)
测试结果表明,单个循环中仅融合一对。( r479 div r1002479 )
Performance counter stats for process id '22597':
120,459,876,711 cycles
35,514,146,968 instructions # 0.29 insn per cycle
17,792,584,278 r479 # r479: Number of uops delivered
# to Instruction Decode Queue (IDQ) from MITE path
50,968,497 r4002479
17,756,894,879 r1002479 # r1002479: Cycles MITE is …Run Code Online (Sandbox Code Playgroud) 我正在研究mips r10000的不同管道阶段.该论文称处理器每次从指令缓存中每个周期取出4条指令.但是指令缓存的延迟必须超过一个周期,但我不知道指令缓存的确切命中延迟,Haswell处理器中L1数据缓存的命中延迟大约是4个周期.
因此,如果我们假设L1指令缓存延迟是3-4个周期,那么处理器如何在每个周期中获取4个指令?
我这里有一个测试题。
哪些指令可能会减慢处理器的工作速度,然后流水线不会预测(分支预测)进一步的执行方式?
可能的答案: JGE | 添加 | 订阅 | 推 | JMP | JNZ | 多| JG | 称呼
如果我们谈论分支预测,JGE、JMP、JNZ 和 JG 是要走的路吗?
我查看了关于分支目标预测器的维基文章;这有点令人困惑:
我认为当 CPU 决定下一步要获取哪些指令(进入 CPU 管道来执行)时,分支目标预测器就会发挥作用。
但这篇文章提到了这样的一些观点:
指令缓存获取指令块
扫描块中的指令以识别分支
那么,指令缓存(==我想象的 L1i)是否基于某些分支目标预测数据(预)取指令?...
或者只是这篇文章暗示了x86以外的东西......好吧,或者我误解了一些东西
5 级流水线 CPU 具有以下阶段顺序:
\nIF \xe2\x80\x93 从指令存储器中获取指令。
\nRD \xe2\x80\x93 指令译码和寄存器读取。
\nEX \xe2\x80\x93 执行:ALU 运算,用于数据和地址计算。
\nMA \xe2\x80\x93 数据存储器访问 \xe2\x80\x93 用于写访问,使用 RD 状态下的寄存器读取。
\nWB \xe2\x80\x93 寄存器写回。
\n现在我知道,例如,指令提取是从内存中获取的,这可能需要 4 个周期(L1 缓存)或最多约 150 个周期(RAM)。但是,在每个流水线图中,我都会看到类似的内容,其中每个阶段都分配一个周期。
\n现在,我当然知道真正的处理器具有超过 19 个阶段的复杂管道,并且每种架构都是不同的。但是,我在这里错过了什么吗?如果在 IF 和 MA 中进行内存访问,那么这个 5 级流水线是否需要几十个周期?
\n在 x86_64 中,没有 64 位地址的直接跳转。只有一个 32 位的。通过间接跳转,我理解在分支预测发挥作用之前必须解决管道一次。我的问题是:在 64 位中没有办法在第一次执行时进行 1-3 个周期的跳转吗?
我在这里查看了最佳答案无条件分支和无条件跳转(MIPS 中的指令)之间有什么区别?
它说分支允许条件,因此指令的格式与无条件的跳转不同。
然而,我见过像 jl 和 je 这样的跳转,它们之前使用 cmp 来设置条件标志,所以在我看来,这些跳转的行为与分支相同,这会减少分支与分支的差异。跳。那么在某些方面我是否可以考虑将 jl 和 je 等指令作为一种分支指令?除了格式之外,我看不出有什么区别,但如果我们考虑执行的话,它似乎并不重要。
我之所以特别问,是因为有人说我需要使用分支,但是,我在 godbolt 中看到的每个算法似乎主要由跳转组成,因此如果不自己编码就不可能获得分支,这不是什么我们已经在这个模块中完成了,所以我没有能力这样做。
x86 ×7
assembly ×5
x86-64 ×4
intel ×3
cpu ×2
jit ×2
avx ×1
branch ×1
cpu-cache ×1
gcc ×1
machine-code ×1
mips ×1
nasm ×1
optimization ×1
performance ×1
pipeline ×1
pipelining ×1
processor ×1
risc ×1