为什么先进的arm架构的苹果其CPU单核性能可以远超腐朽的X86阵营的AMD还有intel?
![]()
前言
叠甲:所有观点仅代表个人观点;所有阐述均经过了极度抽象简化以便于理解;所有阐述仅举例,不代表契合实际产品,如有雷同纯属巧合。
本文的内容如下:首先介绍x86引入解码簇的原因,接着简述解码簇的基本结构,然后介绍OD-ILD优化,接着介绍hardware toggle point优化,最后解释zen5的解码簇设计选择和背后可能的挑战。
可恶的非定长指令集
为什么x86需要使用解码簇的设计?为什么只有x86才采用了解码簇的设计?这不得不从一个看似非常不起眼的ISA特性讲起了:指令长度。对于Arm、Loongarch这样的指令集而言,所有的指令长度均为4Byte,我们将这样的ISA称为定长指令集;而x86的指令可以是1Byte,可以是3Byte,也可以是15Byte等等,我们将这样的ISA称为非定长指令集。我们不妨对比一下两类指令集的解码过程
定长指令集解码
对于定长指令集,所有的指令长度均为(比如)4Byte,这就意味着指令在地址空间内的摆放都是4字节对齐的。我们在将内容未知的指令码送入译码器前就可知每一条指令的边界何在,因此我们可以直接将每条指令的指令码直接送入译码器。
直接译码
在不将任何rename工作前移的前提下,所有译码器的工作近乎可以视为并行,我们可以方便得扩展译码宽度,easy!
非定长指令集解码
(我们不考虑prefix这些变态特性!)对于非定长指令集,指令的长度任意,这也就意味着指令本身可能从地址空间内的任意位置开始。可恶!我们突然发现两眼一抹黑,15Byte的内容可能是一条15Byte的nop指令,也可能是15条1Byte的nop指令!从未有过如此美妙的开局。
先确定指令边界,再译码
译码过程出现了一个额外的步骤:指令边界划分。这使得我们的译码工作变成了半串行而非全并行,因为只有知晓了第一条指令的末尾才能确定第二条指令的起始,以此类推,译码器越多,相关链越长。这条相关链让x86的译码近乎不可能在一个时钟周期内完成,我们有没有办法大幅优化这个过程呢?可以!加钱!

直接译码,确定指令边界后再选择有效结果
我们用空间换时间,穷举所有可能!以8Byte内容待译码为例,我们不妨将其拆分成从0-7Byte开始的8种可能,假设每一种可能都足以让我们得到一条合法指令。我们将8种假设送入8个译码器,再根据前位译码器的输出(指令长度)从后续译码器的结果中选择有效的结果。依此,在定长指令集中只需要2个译码器的事,我们花了8个译码器才解决,而且解决得并不完美。首先仍然有部分串行的内容;其次,如此巨大的扇入扇出、复杂的shuffle和结果select仍然是时序难以承受的;最后,如此巨大的资源消耗效率极低、面积的扩大也会反过来导致时序恶化,我们可能还是得拆分流水级来保证频率。天哪,难道我们陷入死局了吗?
Uop Cache
既然非定长指令的译码如此费力,我们能不能避免?可以啊,我们将译码后的结果存储下来,之后直接读取不就可以跳过整个译码过程啦?于是我们看到了Uop Cache。Uop Cache的本质是按照基本块组织的、存储译码后指令的指令Cache。在Uop Cache中我们可以让译码后的中间内容保持定长,从某种程度上将非定长指令集在处理器内部转化为了定长指令集,进而允许我们相对方便得扩大从Uop Cache取指的宽度,而无需顾忌译码复杂度爆炸的问题。
然而Uop Cache显然不是完美的,否则全剧已终:
译码后的指令信息密度大幅下降,例如原先4Byte的指令码可能需要8Byte去存储。这也就意味着使用同量的SRAM,Uop Cache的有效容量只有ICache的一半,无法忍受!凭什么我们x86要比别人多费这么多劲儿还收益这么低?
以基本块组织的Uop Cache带来了fragmentation和duplication的问题,导致空间利用效率低,进一步导致有效容量低。好家伙,和第1点加在一起低上加低。
缺乏预取,难以应付大指令足迹内容。
我们不详细阐述Uop Cache的故事,这不是本文的重点。那么我们能不能把Uop Cache踢掉又保证性能呢?
解码簇
反思,再反思,我们发现一切问题的主要来源都是我们无法确认指令的边界,如果我们能够知道指令从何处开始,不就能够解决这个问题了吗?我们在哪些情况下可以100%确认指令从哪里开始呢?跳转!程序流跳转到一个新地址,这里必然是一条指令的开始地址。而且我们会发现,一般而言程序中每5条指令会有1个分支指令,每2条分支指令会发生一次跳转。

译码簇
我们发现,taken branch帮我们斩断了译码簇之间的译码器间的相关链。尽管每个簇内部的译码器之间是部分串行的,但是相关链深度已然大幅缩减,组合爆炸的问题大幅缓解。不过我们显然不会到此满足,加料开始。
OD-ILD
Uop Cache的问题在于存储的内容太多了,我们并不想付出如此多的代价,如果我们只存储一点必要信息,比如指令长度呢?OD-ILD应运而生。我们在取指时同时读取OD-ILD(实际上也可以将其视为I-Cache的一部分),倘若命中,则和指令内容一起供给给译码簇。以此我们能够提前划分指令边界,跳过部分译码逻辑,减少流水线深度,并极大减少了簇内译码器间的相关性。

OD-ILD命中
然而OD-ILD的内容与Uop Cache一样是runtime训练得到的,当我们第一次访问某个代码段时OD-ILD会发生miss,此时我们回退到基础的译码簇流水线,确定指令边界后将相关预译码信息填入OD-ILD中。在这种情况下还要经历额外的流水线惩罚(因为要写OD-ILD)。到此为止了吗?似乎还有点不对劲?
Hardware toggle point
一般而言程序中每5条指令会有1个分支指令,每2条分支指令会发生一次跳转。
我们不禁有几个疑惑:
如果极端情况下许许多多的指令中都没有跳转,即超长基本块,那么此时岂不是多译码簇就失效了?工资白发?
就算如此所说,似乎一个基本块也得有10条指令。那么每10条指令才能切换一次译码簇,岂不是没法将多译码簇用来提升处理器的峰值带宽了?
taken branch在程序流中的出现太靠天意,似乎不能全指望程序员,不如让处理器能够自己往程序里添加taken branch,hardware toggle point应运而生。
当我们探测到一段程序内没有如我们预期得出现taken branch时,我们向BTB中插入一个fake taken branch,尽管指令流中没有,但是处理器仍会将其视作taken branch,每当执行到此处时就可以切换至另一个解码簇了。fake taken branch的插入会占用BTB的表项目,而且遵循一定的规则,例如只有常被执行的指令块我们才会尝试为其插入fake taken branch。至此,我们终于能够充分利用多解码簇了,不仅摆脱了Uop Cache,还能够兼顾单线程的峰值性能。
Zen5的选择
等等,为什么zen5的多解码簇根本没法同时运用于一个线程,这不是光速打脸吗?其仅支持在双线程运行时,每个线程独占一个译码簇。

我们不妨分析下zen5目前遇到的困难,zen5相较各种mont的最大区别是它支持SMT,为什么SMT会给多解码簇带来挑战呢?我们不得不回到SMT的资源划分问题上,实际上在大多数的SMT实现中,ROB、instruction queue等队列是静态平分给每个线程的,这些队列有一个共同特点:他们借助类FIFO的特性天然维持保序特性。这也导致了一个问题,即他们天然厌恶队列内出现内容空洞。当某一个线程出现例如分支预测错误这样的需要flush流水线的操作时,倘若一个译码簇的类FIFO队列内存在两个线程的指令,我们
要么根本无从将他们区分,只能全部flush。这会导致一个线程的性能损失扩大到两个线程的性能损失,无法忍受。
要么flush掉属于一个线程的指令,并在类FIFO队列中留下大片的空洞。这给我们的队列管理带来了巨大的问题。
因此,我们需要大量的额外设计让SMT能够混合利用多译码簇。嗯,未来可期。更何况zen5并没有去除Uop Cache,我们还是静待zen6吧,同时值得期待的还有skymont的后继darkmont,我们极有可能看到e core的完全体登场,intel是如何处理SMT问题的呢?
说完了这些继续说
为什么Apple Silicon在R24中这么强(一)—LAP
如你所见,今天我们要讲的是,为什么,Apple Silicon在Cinebench R24中表现如此的强,连隔壁的X86都甘拜下风,尤其是M4,我看我们的贴吧老哥都跑上192了,简直是非常的厉害。
首先我们需要明白R24是一个比较重LSU的一个benchmark,那么M4刚刚好大提升的就是这部分,那么今天我们引入我们的主题,LSU,LSU是CPU中很重要的一个部分,我们首先需要了解一下什么是LSU。
LSU 是 “Load–Store Unit”(加载存储单元)的简称,是一个专门负责处理所有访存指令(即加载 load 和存储 store 指令)的执行单元。下面从多个角度详细介绍其功能和内部结构。
1. LSU 的主要功能
(1)执行访存指令
LSU 主要负责将程序中发出的 load 指令和 store 指令送入内存系统进行处理。这包括根据指令中给出的基地址、偏移量等信息计算出实际访问的地址,并根据地址从缓存或主存中读取数据(load),或者将数据写回内存(store)。
(2)地址生成与虚实地址转换
为了确定内存中具体的位置,LSU 内通常会包含一个或多个地址生成单元(AGU)。AGU 负责执行简单的算术运算(如加法),将基地址与立即数或寄存器内容相加,从而计算出访问地址。与此同时,在采用虚拟内存的系统中,LSU 还需要将程序使用的虚拟地址转换成物理地址,这一过程一般依赖于 TLB(Translation Lookaside Buffer)来加速转换过程。
(3)处理访存依赖和数据转发
在现代高性能处理器中,指令往往是乱序执行的。LSU 不仅要确保各条访存指令按正确的顺序完成(即满足内存一致性和程序顺序要求),还需要解决因数据依赖产生的潜在冒险问题。例如,若一条 load 指令依赖于一条尚未完成的 store 指令,LSU 可能会通过“数据前向转发”(Store-to-Load Forwarding)的机制直接将 store 指令产生的数据传递给后续的 load 指令,从而降低延迟并提高流水线利用率。
2. LSU 的内部结构
(1)Load Queue(加载队列)与 Store Queue(存储队列)
为了管理所有访存指令,LSU 内部一般设计有两个队列:
(2)Load Queue (LDQ): 用于暂存所有待执行的 load 指令,在这些指令执行前,会先进行地址计算和依赖检查;
(3)Store Queue (STQ): 用于记录所有 store 指令,特别是在乱序执行中,store 指令可能提前计算出地址和数据,但数据真正写入内存时需要保证按程序顺序提交。通过存储队列,LSU 能够检测 load 与 store 之间的依赖关系,并在可能出现数据竞争时采用转发技术。
(4)地址生成单元(AGU)
AGU 负责将 load/store 指令中的地址计算任务具体化,结合基地址与偏移量,生成最终的内存访问地址。这一步骤对提高访存操作的效率至关重要。
(5)与缓存/内存系统的接口
LSU 是 CPU 内部执行单元与外部内存系统之间的桥梁。它不仅向缓存(如 L1 数据缓存)发出数据请求,而且还接收缓存或内存返回的数据。在缓存命中情况下,数据可以迅速从缓存传递给 CPU;而在缓存未命中时,LSU 会协调从更低级别内存中取数,同时管理等待和重排操作。
总之,LSU(Load–Store Unit)是 CPU 中专门负责处理内存访问操作的执行单元。它通过内部的地址生成、Load/Store 队列以及数据转发等机制,确保 load 和 store 指令能够高效且正确地与内存系统交互。在支持乱序执行和高指令并行度的现代 CPU 设计中,LSU 的高效实现对于整体性能至关重要。这种设计既要求严谨的硬件逻辑,也需要在系统级别上考虑访存延迟、依赖检测以及缓存接口等多个方面,从而实现既严谨又高效的内存操作管理。这也就是我常说的,一个优秀的u-arch三要素,BPU,LSU,prefetcher,把这三个能够做好,才能达成performance和energy的最强。
那么,Apple在LSU有没有过人之处呢?有的朋友,有的,而且是人无我有的这种。前段时间,春节期间,一个有关CPU side‐channel attack(CPU侧信道攻击)的paper吸引了我的兴趣,我发现了2个有关LSU part的巧思,那么接下来我会与你们分享这个技术。
首先,我们来谈谈LAP(Load Address Predictor),Apple Silicon 处理器(M2、M3、A15 及更新型号)中包含一个 Load Address Predictor (LAP),用于预测加载指令(load instruction)的目标地址,以减少数据访问延迟,提高执行效率。其原理:
(1)LAP 记录过去执行的相同load instruction(加载指令)访问的address(地址),并基于这些history address(历史地址)进行predict(预测)。
(2)如果predict(预测)的address(地址)已经缓存(cached),CPU 将会在正式calculate(计算)之前,使用该predicted address(预测地址)preload data(提前加载数据)。
(3)这一机制允许 CPU 在数据尚未真正确定的情况下,利用predicted value(预测值)执行后续指令,从而实现更快的计算。
(4)但如果预测错误,CPU 会丢弃错误的计算结果,并回滚到正确的执行路径。
LAP的基本作用也很简单,LAP是一种basic on 数据流推测执行(Data Speculation Execution) 机制,专门用于优化加载指令(Load Instructions)的execution efficiency(执行效率)。相较于traditional Control Flow Speculation(传统的控制流推测),LAP关注的是DSE(Data Speculation Execution),通过remember same load instruction(记录相同加载指令)的history address(历史访问地址),LAP是predict(预测)该指令未来可能访问的地址。如果(predicted address)预测地址cached(已缓存),CPU 直接在推测窗口中加载该地址的数据,并执行后续指令,而无需等待实际内存访问完成。这样就可以提高整体的efficiency(效率)。

如图LAP 预测的地址如果已经在缓存中,则加载时间明显更短,说明LAP 仅能在缓存命中时生效。最后如果predicted miss(预测错误),CPU 丢弃推测结果,并恢复到正确的执行路径。

由图可知:SA+RV(上):访问模式保持 步长一致,但数据存储为随机数。Random(下):访问地址和数据都是随机的。目的是分离 LAP 对地址预测和数据内容的影响。

结合这两张图,说明即使数据随机,只要地址访问模式有规律,LAP 仍然可以预测。说明 LAP 仅预测加载地址,而不考虑数据值。LAP 主要依赖地址模式(Strided),而与数据值无关。训练阈值 = 500 次,表明 LAP 需要足够多的训练数据。
那么我们来看看LAP的具体工作原理,首先LAP有属于自己的激活条件。至少 500 次same load instructions(相同的加载指令)才能激活 LAP feature。并且在某些 CPU 上(如 M3),需要更高的training Length(训练次数)(~1000 次)。
那么,除了training length,还有Stride(步长)的限制。LAP只能学习固定步长(Strided Access Patterns) 的加载地址模式,stride必须在 255 字节以内(8-bit stride tracking),如果步长大于 255 字节,LAP不会触发预测。

如图就是通过不同的访问模式(Striding和Random),检验LAP是否适用于SAP(Strided Access Patterns),随机访问模式是否影响预测。结果就是LAP 对固定步长访问模式有明显优化作用(Striding 模式加速)。对随机访问模式无优化作用(Random 模式执行时间较长)。

该图展现了不同 Apple 处理器的 Striding vs. Random 运行时间,由图可知,M1 处理器不支持 LAP,M2/M3 P-core 具备 LAP 。M2/M3 P-core 在 Striding 访问模式下具有预测优化,随着训练次数增加,LAP 提前加载数据,提高执行效率。M3 需要更长的训练时间(320 次 vs. 120 次),可能表明 Apple 在 M3 处理器上调整了 LAP 触发条件。但是,M2,M3的E-Core并不支持LAP技术。可能和P与E的stress分配有关。

结合这张图我们就能清楚的发现,横轴:训练长度(Training Length),纵轴:步长(Stride)。颜色深度:蓝色区域表示 LAP 高激活概率。黄色区域表示 LAP 失效。由图可知,LAP 需要 ≥ 500 次训练才能稳定激活。步长必须 ≤ 255 字节,超出范围 LAP 不会预测。
最后,预测地址的缓存要求:LAP 不会主动预取(Prefetch)数据,而是仅在预测地址cached(已经缓存)在 L1 cache时才会推测加载。如果预测地址未缓存,LAP直接终止推测执行,不会进行推测性加载。
接下来,我们说说training和retire。首先LAP通过检测same load instructions(相同加载指令)在不同时间访问的address model(地址模式)来学习预测:
1.记录加载指令的历史地址:CPU 监视特定的加载指令(Load Instruction),存储其最近几次访问的地址。如果访问模式符合固定步长(Strided Pattern),LAP进入激活状态。
2. 在足够多的训练后,LAP 开始predict:例如,如果 ldr x0, [x1] 过去几次load address(加载地址)分别为:0x1000 → 0x1020 → 0x1040 → 0x1060。发现stride(步长)为 0x20(32B),LAP 预测下一次地址为 0x1080。
3.推测执行(Speculative Execution):如果 0x1080 在缓存中,CPU 直接使用该值进行计算,而不会等待实际地址解析完成。如果 0x1080 不在缓存,LAP stall prediction,CPU retire(回退)到正常加载流程。

如图,基于strided access进行next load address prediction(下一个加载地址推测)。
如果 LAP address prediction miss(预测的地址错误)(即 x1 实际指向 0x1060,但 LAP 预测 0x1080),CPU 需要回滚执行状态:CPU 发现预测错误,丢弃错误的推测执行结果。恢复正确执行路径,重新从 x1 指向的正确地址 0x1060 进行加载。但微架构状态(如缓存和分支预测表)不会回滚,可能导致信息泄露风险。
LAP 预测执行窗口(Speculation Window)
论文的实验测量了 LAP 在 Apple M2、M3 处理器上的 推测执行窗口,如图:

1.最长可达 600 CPU 周期。
2.可执行多个后续指令,包括:计算操作(Arithmetic Operations),条件判断(Conditional Branching),甚至可以调用未被程序正式调用的函数(如图)。

LAP的推测窗口影响因素
1.缓存状态:
2.如果预测地址cached:Speculation Window deeper较深,可执行长达 600 个周期的推测计算。
3.如果预测地址不在缓存:LAP 预测终止,Speculation Window shortest极短(< 100 个周期)。
4.加载指令是否 RAW 依赖(Read-After-Write):如果 ldr 指令的目标寄存器在随后指令中有数据依赖,则Speculation Window maybe short。
5.执行核心(P-Core vs. E-Core):E-Core 没有 LAP 机制,推测窗口不存在。
那么,LAP与控制流预测的区别是什么呢?传统Spectre依赖控制流预测(Branch Prediction),如:if (x > 10) { ... } else { ... }CPU 可能错误预测分支,导致执行错误路径的指令。而,LAP 是数据流预测:其预测 ldr x0, [x1] 访问的地址,而非控制流路径。
第二点,LAP与硬件数据预取(DMP)的区别:数据预取器(DMP):如果加载的值类似于指针,DMP 可能会提前预取数据。LAP 不主动预取数据,但会使用预测地址的缓存值进行推测计算。
那么这么好的技术,在什么Apple Silicon和Device有呢?
LAP 适用范围在论文的实验结果表明:LAP 仅存在于 Apple M2、M3 处理器的高性能核心(Performance Core, P-core):M1 处理器:未发现 LAP 机制;
M2 处理器:P-core 存在 LAP,E-core 没有 LAP
M3 处理器:P-core 存在 LAP,但需要更多训练,E-core 没有 LAP
Apple 移动设备(A 系列):A15 及更新型号(iPhone 13 mini、iPhone 14 Pro Max、iPhone 15 Pro Max、M4 iPad Pro):支持 LAP。
iPhone 13(标准版)和 iPhone 12 及以下机型没有 LAP。
哦,这可太可惜了,这么好的技术Firestorm刚好没有,我们可怜的类似物Oyron也没有。
总结一下,LAP 是 Apple Silicon 处理器中的新型数据流推测执行机制,用于 优化加载指令的执行效率。LAP 需要特定训练条件(500 次相同加载、步长≤255 字节)才能可靠激活。LAP 预测仅在预测地址已缓存时生效,否则推测执行窗口会缩短。LAP 允许长达 600 CPU 周期的推测执行,可能影响数据安全性。LAP 仅存在于 Apple M2/M3 的 P-core 上,E-core 不支持该功能。LAP 与 Spectre(控制流预测)和硬件数据预取(DMP)不同,是另一种微架构优化策略。
那么,接下来我们推测一下其硬件实现,首先就是LAP在CPU Architecture中的位置,LAP 作为加载地址预测器(Load Address Predictor),主要影响加载/存储单元(Load/Store Unit, LSU) 和Frontend Prediction feature(前端预测机制),其大概率存在于:
1.L1 数据缓存(L1D Cache):处理器执行 ldr 指令时,如果 LAP 预测的地址已缓存,数据可以立即获取,从而减少访存延迟。如果数据未缓存,LAP 预测终止,CPU 需要正常访问内存。
2.重排序缓冲区(ROB, Reorder Buffer):由于现代CPU采用乱序执行(Out-of-Order Execution, OoO),LAP 预测的地址加载结果可能会影响后续指令的执行顺序。
3.通过预测表(Prediction Table)进行实现:存储器history address(地址历史),用于学习步长模式(Strided Pattern)。可能采用按 PC(Program Counter)索引的结构,类似于分支预测器(Branch Predictor)例如TAGE,所以我们在这可以思绪飞扬一下,why not,给TAGE上增加对应的LAP table呢?
接下来我们详细的分析一下LAP可能的硬件组成,
1.负载地址预测表(LAPT, Load Address Prediction Table)
作用:存储过去的加载地址历史,用于模式学习和预测下一个地址。
存储结构:PC 作为索引:LAPT 可能使用加载指令的程序计数器(PC) 作为索引,以便针对特定的 ldr 指令进行学习。
步长记录(Stride Tracking):记录相邻两次加载之间的步长,如果步长固定,则 LAP 进入预测状态。
可能的存储格式:

存储深度:论文实验表明 LAP 需要训练至少 500 次才能稳定激活,因此 LAPT 可能存储500 条以上的历史记录。
2.预测逻辑(Prediction Logic)
模式检测(Pattern Matching):当 ldr 指令被执行时,LAP查询LAPT,检查该指令的历史加载地址是否具有固定步长。如果连续的地址访问符合 addr_n = addr_{n-1} + stride,LAP预测 addr_{n+1} 并提前加载数据。
阈值控制(Threshold Control):论文实验显示 500 次训练后 LAP 才能触发,因此预测逻辑可能有一个训练阈值,避免误预测,通过计数器存储每条 ldr 指令的匹配次数,防止过早触发预测。
3.预测触发器(Prediction Trigger)
加载指令执行时,LAP 检查是否满足预测条件:
如果满足:计算预测地址 = last_address + stride。如果预测地址已缓存,则提前加载数据到 CPU 的寄存器。进入推测执行窗口(最大 600 周期),执行后续指令。
如果不满足:LAP 预测终止,CPU 进行正常的内存访问。
4.失效回退(Misprediction Recovery)
如果预测错误(即 ldr 指令的实际地址不等于预测地址),则:
撤销推测执行:丢弃所有基于错误预测值的计算结果。
恢复正确执行路径:重新从 ldr 指向的正确地址读取数据。
但微架构状态(如缓存内容)不会回滚,可能引发信息泄露风险(如 Spectre)。
根据以上的分析,我们发现有三种实现方式。
1.类似于分支预测器的硬件实现
LAP 预测逻辑可能采用分支预测器(Branch Predictor)类似的结构:
LAPT(类似 BTB):记录 ldr 指令的历史加载地址。采用 PC 作为索引,类似于分支预测表(Branch Target Buffer, BTB)。预测触发逻辑(类似 GShare):计算步长,并基于步长模式预测下一个加载地址。可能采用哈希索引(Hashed Indexing) 以减少存储需求。
2。结合硬件数据预取(Data Prefetching),LAP 和数据预取器(DMP)的区别:
DMP(Data Memory-dependent Prefetcher) 会基于数据模式预取地址,而 LAP 只预测加载指令的地址。LAP 不会主动预取数据,只会在预测地址已缓存时触发推测加载。
3.结合乱序执行(Out-of-Order Execution)
LAP 可能集成到 乱序执行引擎(OoO Execution Engine):存储-加载转发(Store-to-Load Forwarding):如果预测地址匹配 L1 缓存,则加载数据。指令队列(Instruction Queue)优化:预测成功时,CPU 直接使用加载值,不需要等待访存完成。
不过,以上三个方法我或许会更青睐集成进BPU中。
为什么Apple Silicon在R24中这么强(二)—LVP
那么,一篇我们说完了LAP,其实还有一个技术,LVP,那么,LVP是什么呢?LVP(Load Value Predictor, 负载值预测器)。其是一种u-architecture优化技术,用于缓解Read-After-Write (RAW) 依赖带来的性能损失。RAW依赖通常会强制 CPU 顺序执行相关指令,影响流水线的并行性。LVP通过stress instructions prediction(预测负载指令)的return value(返回值)来Improve the parallelity(提高并行度),从而improve process performance(提升处理器性能)。
首先,我们需要理解什么是RAW依赖,当一个指令读取的值取决于前面某个存储操作的结果时,必须等前面的存储操作完成,CPU 才能执行这个指令。例如:
int x = load(mem_address);
int y = x + 1; // 依赖于 load 指令
如果 load(mem_address) 需要从主存中取数据(延迟高达数百个周期),那么 y = x + 1 指令必须等待 load 完成才能执行,从而影响流水线性能。
那么LVP是怎么做的呢?LVP通过观察historical load value(历史负载值)并predict(预测)未来的load value(负载值),从而在内存访问完成之前提供一个估算值,使得依赖该值的指令可以提前执行:
1.学习模式:LVP 观察load instruction(负载指令)多次执行的结果,如果某个load value(负载值)始终一致,它就会将该值作为预测结果。
2.预测模式:如果 LVP 发现某个load instruction(负载指令)过去返回的值是恒定的,它会在该指令下一次执行时立即提供预测值,而不等待实际内存访问完成。
3.验证与回滚:如果预测错误,CPU 会丢弃错误的计算结果,并重新执行相关指令。
我们来看这个示例,LVP 在循环计算中的典型应用:
for (i=0; i<N; i++)
val = arr[val];
LVP观察到 val总是被赋值为foo,于是在未来的迭代中直接预测val = foo,减少因 RAW 依赖造成的流水线停滞。如果预测错误,则回滚并重新执行。

由图所示, Load Value Predictor (LVP) 在优化 Read-After-Write (RAW) 依赖时的工作流程。
具体包含 4 个步骤:
1.CPU 观察历史负载值,发现 val 的加载值始终为 foo。
2.LVP 激活,开始预测未来的 load 结果。
3.CPU 并行执行两条路径:
投机执行路径:使用预测值 foo 计算后续代码。
实际执行路径:读取内存中的真实值。
4.结果确认:
如果预测值正确,则提交投机执行的结果。
如果预测值错误,则回滚并重新执行正确的计算。
从中我们可以得知, LVP 通过投机执行来缓解 RAW 依赖,减少等待存储器访问的时间,提高流水线并行度。并且LVP通过 “学习过去的负载值” 来提高预测准确性,并通过回滚机制保证错误预测时 CPU 状态的一致性。
接下来我们看看LVP具体怎么实现的,我们发现,M3、M4 和 A17 Pro 处理器都实现了 LVP,但其设计上有一些限制:
1.预测范围,支持预测的数据宽度:1B(字节),2B(半字),4B(字),不支持 8B(双字)(即 64 位),推测是为了防止 LVP 直接预测指针,影响内存安全。
2.LVP仅对常量值有效:如果load instructions value(负载指令的值)是恒定的(例如 foo),LVP会学习该值并进行预测。如果负载值是递增或递减的(stride pattern),LVP不会激活。
3.局部性与训练机制:LVP基于指令地址(PC)进行训练,每个特定的load指令最多可以存储72个不同地址的预测值(即 LVP 采用了类似于 cache set-associative 设计)。循环展开会导致LVP失效,因为每次展开的 load 指令地址不同,LVP 无法积累足够的历史信息。

由这张图,我们可以发现,M2 P-core:没有 LVP,执行时间线性增长。M3 P-core:LVP 生效,执行时间显著减少(约 50%)。M2 E-core 和 M3 E-core:均未显示 LVP 迹象,执行时间与对照组相同。所以,我们可以获得结论,LVP 仅在 M3 的 P-core 上生效,而 E-core 不包含 LVP 机制。而M2 处理器不具备 LVP,意味着该优化是 M3 及更高代芯片的新特性。

该图展示了 LVP 在不同 load 宽度(1B, 2B, 4B, 8B)下的表现。在 M3 P-core 上,分别测试 1B、2B、4B 和 8B 的 load 指令,并记录执行时间。从结果来看,1B, 2B, 4B 负载 LVP 成功预测,执行时间降低。8B 负载 LVP 仅在返回值为 0 时激活,否则失效。所以,Apple 可能通过限制 8B 负载的预测,防止LVP直接预测 64-bit 指针值,以减少潜在的安全风险。

该图对比了 LVP 在加载固定值 vs. 递增值时的表现,左侧(恒定值),LVP 预测生效,执行时间减少。右侧(递增值),LVP 不生效,执行时间与随机值类似。我们可知,LVP 仅适用于恒定的load值,无法预测递增/变化模式。

该图展示了在循环未展开 vs. 完全展开情况下 LVP 是否生效。结果显示,循环未展开(左图):LVP 生效,执行时间减少。循环展开(右图):LVP 失效,执行时间回归到正常水平。所以,我们可知,LVP 依赖于特定指令地址,如果指令地址因循环展开而改变,LVP 训练状态会丢失,导致预测失效。

首先,该图展示了 LVP 能够同时存储的最多指令数(72 个)。可知,LVP 采用Local Indexing Strategy(局部索引策略),最多能同时维护 72 条 load 预测条目,可能是 4-way set-associative cache(4组相连缓存结构)。
接下来我们看看LVP的行为特性
1.训练与预测精度,训练阶段:需要约 250 次training load(训练加载),LVP 才会达到高置信度,并稳定预测值。训练时,如果 CPU 发现负载值 始终一致,LVP 进入预测模式。
2.错误预测时的行为:如果 LVP 预测错误,CPU 仍然会使用预测值进行计算,但会在之后检查真实值并回滚执行状态。
3.预测状态的持久性:如果 CPU 核心长时间未执行 LVP 相关指令,预测状态可能会逐渐丢失。LVP 预测状态不会跨进程共享,说明 LVP 可能使用进程 ID(PID) 作为indexing tag(索引标签),防止预测值泄露到其他应用程序。

该图展示了 LVP 训练所需的最少 load 次数,在 250 次之后,预测才变得稳定。我们可知,训练 load 次数至少需要 250 次,之后 LVP 预测可靠度提高。

并且如图,测试了 LVP 预测状态是否会随时间丢失。结果表明,如果没有其他负载干扰,LVP 状态可持续长达 1 秒。但是强负载会导致 LVP 训练状态衰减。所以,LVP 预测状态较为持久,但会被 CPU 其他高强度任务冲刷掉。

由该图可知,测试了在 LVP 预测错误时,CPU 会继续执行多少条指令。预测错误时 最多执行 110 条 mul 指令(330 cycles)。如果 load 命中缓存,仅执行 10 条指令(30 cycles)。由此可知,预测窗口的深度取决于 load 是否命中缓存,如果 load 需要从内存获取数据,预测窗口会更长。
接下来我们看看其与u-architecture的关系,
1.LVP 与乱序执行:乱序执行(Out-of-Order Execution, OoOE)允许 CPU 在数据准备好之前执行无关指令,但 仍然受限于数据依赖。且LVP 进一步提升并行性,通过预测数据值,使得即使存在数据依赖,也能让后续指令提前执行。
2.LVP 与投机执行:投机执行(Speculative Execution)允许 CPU 在分支指令尚未确定时继续执行可能的路径,如 Spectre 类漏洞所利用的机制。LVP 与投机执行不同:投机执行预测的是控制流分支预测(branch prediction),LVP 预测的是数据流数据预测(data prediction)。两者都可能导致瞬态错误计算,但 LVP 更专注于提高数据访问的并行性。
那么LVP 的实际影响呢?首先运行时性能提升,LVP 可以 显著减少 RAW 依赖带来的延迟,特别是在 循环密集型计算 中,可能提升 近 2 倍 的吞吐量。在 M3 处理器上,上面的实验显示:如果 load 值是恒定的,M3 的 LVP 可使执行时间减少近 50%。如果 load 值是随机变化的,LVP 不会生效,导致性能下降到正常水平。但是新技术也会对微架构安全性产生影响,手下吗需要小心选择可预测的负载,否则可能误导后续计算。其次需要控制误预测带来的回滚开销,否则可能适得其反,导致比传统 RAW 依赖更严重的性能损失。最后需要配合其他投机执行保护机制,确保在预测错误时正确恢复 CPU 状态,避免影响数据一致性。
总结一下吧,苹果 M3 处理器的 LVP 机制是为了提高数据访问效率而设计的微架构优化,主要作用是预测负载指令的返回值,减少 Read-After-Write 依赖带来的流水线阻塞。
(1)优势:
1.可以大幅提升LSU performance(内存访问性能),尤其是在loop calculate(循环计算) 和instructions pipeline(指令流水线)优化方面。
2.采用了多层次保护措施(不预测 64-bit 指针、不跨进程预测),减少了安全风险。
(2)局限性:
1.仅支持 1B、2B、4B 负载预测,不支持 8B 负载(防止指针推测)。
2.不适用于递增/递减负载值(只对恒定值有效)。
3.不能跨进程共享状态,限制了部分全局优化的可能性。
总体而言,LVP 作为 CPU 设计的一种重要优化技术,未来可能会进一步发展,以支持更广泛的数据模式,同时增强安全性。那么很显然,这个东西Firestorm类似物的产品依然没有,哦好可惜啊
接下来我们对LVP进行一下硬件推测。LVP 作为一个微架构优化单元,主要用于记录和预测 CPU 加载指令返回的值,使 CPU 在数据尚未到达时仍能继续执行依赖该值的指令。它的硬件实现可能包括:LVP 存储结构(预测缓冲区),LVP 训练和更新逻辑,LVP 预测和投机执行机制,LVP 回滚和错误恢复机制,LVP 与其他 CPU 组件的交互。
1.LVP 预测缓冲区(Prediction Storage)
LVP 需要一个存储结构来记录哪些加载指令值得预测,以及其历史预测值。
(1)结合实验数据,我们推测:LVP 采用 4 路组相联缓存结构(4-way set-associative),类似于 CPU 的 L1 缓存。最多可同时追踪 72 个不同的加载指令,表明其存储容量较大,可能有一个 72-entry 的预测表,每个 entry 记录一个加载指令的预测信息。
(2)存储索引基于 PC(Program Counter):由于 loop unrolling(循环展开)会导致 LVP 失效,说明 LVP 存储状态是按指令地址(PC)进行索引的,而不是全局的加载值记录。可能采用 指令地址的哈希值作为索引方式,以减少存储占用。并且可能会考虑页内偏移(page offset) 来减少索引冲突。
(3)预测数据的存储格式:由于 LVP 仅适用于 1B、2B、4B 宽度的加载值,可能使用按字节存储的表结构 来存储预测值,而非直接存储完整的 64-bit 值。不会预测 8B(64-bit)值,但如果为 0 则会预测,表明 Apple 可能采取了一个硬件安全策略,避免直接学习和预测指针值。
2. LVP 的索引与训练机制,LVP 需要在 CPU 执行过程中不断观察和学习哪些加载指令的返回值是可预测的。
LVP 训练机制:
(1)训练数据的收集:LVP 仅对固定加载值(constant loads)进行训练,而不会训练步长递增的加载值(striding values),表明 LVP 采用了一种简单但安全的模式匹配机制。实验表明大约 250 次相同的加载值执行后,LVP 开始提供可靠预测。在此过程中,每 60 次训练 LVP 的预测可靠性会增加,说明训练数据可能使用了一个 基于计数器的置信度模型(confidence counter):
低于 60 次:不激活预测
60-120 次:部分预测
120-240 次:预测率逐步提高
250 次以上:预测值趋于稳定
(2)训练状态的存储:由于 LVP 在 1 秒内可保持稳定,表明其状态存储可能位于 CPU 内部的独立预测缓冲区,而不是 L1/L2 缓存。LVP 训练状态不会跨进程传播,表明 LVP 存储可能使用 进程 ID(PID)或 ASID(Address Space ID) 作为other index(额外索引),确保预测不会影响其他进程。
3. LVP 的预测和投机执行
预测机制
(1)预测的触发条件:
加载指令的值在历史执行中出现高度重复(即前 250 次均返回相同值)。
该指令的 PC 地址已被 LVP 记录。
该指令的数据宽度在 1B、2B 或 4B 之间。
(2)投机执行窗口:
如果加载值未命中缓存(cache miss),LVP 的投机执行窗口最长可达 330 cycles。
如果加载值命中缓存(cache hit),LVP 的投机窗口缩短至 30 cycles。
这表明 LVP 可能受到 CPU 投机执行流水线的限制,仅能在一定的窗口内继续执行。
4. LVP 的回滚与错误恢复,如果 LVP 预测值错误,CPU 需要回滚错误的计算结果,并重新执行正确的计算。
(1)错误检测:真实加载值到达后,CPU 会将其与 LVP 预测值进行比较。
如果匹配:则继续执行,不需要任何修改。
如果不匹配:回滚错误计算(类似于投机执行中的错误恢复),并重新执行受影响的指令。
(2)回滚机制:LVP 预测错误后的回滚机制可能类似于现代 CPU 的投机执行回滚:
ROB(Reorder Buffer)管理 LVP 预测的指令结果。
如果 LVP 预测错误,ROB 会 丢弃错误的投机计算结果,并重新执行正确的计算。
5. LVP 与其他 CPU 组件的交互
(1)与 L1/L2 缓存的交互:LVP 主要作用于 L1 数据缓存(L1D)访问,而不会涉及 L2/L3 缓存。只有当 L1D Cache Miss 发生 时,LVP 才会发挥作用。
(2)与投机执行机制的交互:LVP 预测的值直接参与 CPU 的投机执行流水线,并在最终值到达时进行回滚验证。
(3)与 DIT(Data Independent Timing)机制的交互:Apple M3 处理器实现的 DIT(数据无关时间) 机制允许用户态进程关闭 LVP。DIT 机制的启用不需要高权限,表明 Apple 允许开发者手动控制 LVP 的行为,以降低安全风险。
6. 改进方向:提高预测范围和增加安全机制。
总结:Load Value Predictor (LVP) 采用 PC index(索引)+4-way set-asscociative cache(4 路组相联存储结构),结合Counter confidence model(计数器置信度模型)进行predict (预测)。LVP 主要优化 L1D 缓存 Miss 导致的 RAW 依赖,但具有严格的预测范围限制,以避免指针泄露。它与 投机执行流水线、DIT 机制以及 CPU 回滚机制紧密集成,在提升 CPU 性能的同时,提供一定的安全保护。
那么,以上就是我所分享的两个LSU技术,可以看到,Apple在CPU的积累确实很强,其他家没有重视的Apple去做了实现,虽然这只是一个很小的技术,对整体u-arch performance能带来的improve,可能微乎其微,但是,积少成多,造就了目前天花板级别的u-arch,这不是类似物们可以copy到的,平心静气好好做architecture不会差的。
作者yy:这篇文章我尽量在用比较通俗易懂的方式进行着讲解,如有问题请大佬们多多包涵。
论文引用:
Kim J, Chuang J, Genkin D, et al. FLOP: Breaking the Apple M3 CPU via False Load Output Predictions[J].
Kim J, Genkin D, Yarom Y. SLAP: Data Speculation Attacks via Load Address Prediction on Apple Silicon[J].
更多游戏资讯请关注:电玩帮游戏资讯专区
电玩帮图文攻略 www.vgover.com
