跳转至

Qualcomm Oryon 微架构评测

背景

最近借到一台 Surface Laptop 7 可以拿来折腾,它用的是高通 Snapdragon X Elite 处理器,借此机会测试一下这个微架构在各个方面的表现。

官方信息

高通关于 Oryon 微架构有两个 slides,内容可以在以下的链接中看到:

两次内容大体一致,Hot Chips 2024 的内容更加详细,但也出现了一些前后矛盾的地方。

现有评测

网上已经有较多针对 Oryon 微架构的评测和分析,建议阅读:

下面分各个模块分别记录官方提供的信息,以及实测的结果。读者可以对照已有的第三方评测理解。

前端

取指

官方信息:取指可以达到每周期最多 16 指令

L1 ICache

官方信息:192KB 6-way L1 ICache, Fetches up to 16 instructions per cycle

为了测试 L1 ICache 容量,构造一个具有巨大指令 footprint 的循环,由大量的 nop 和最后的分支指令组成。观察在不同 footprint 大小下的 IPC:

可以看到 footprint 在 192 KB 之前时可以达到 8 IPC,之后则快速降到 2 IPC,这里的 192 KB 就对应了 L1 ICache 的容量。

虽然 Fetch 可以每周期 16 条指令,也就是一条 64B 的缓存行,由于后端的限制,只能观察到 8 的 IPC。为了测试实际的 Fetch 宽度,参考 如何测量真正的取指带宽(I-fetch width) - JamesAslan 构造了测试。

其原理是当 Fetch 要跨页的时候,由于两个相邻页可能映射到不同的物理地址,如果要支持单周期跨页取指,需要查询两次 ITLB,或者 ITLB 需要把相邻两个页的映射存在一起。这个场景一般比较少,处理器很少会针对这种特殊情况做优化,但也不是没有。经过测试,把循环放在两个页的边界上,发现 Oryon 微架构遇到跨页的取指时确实会拆成两个周期来进行。在此基础上,构造一个循环,循环的第一条指令放在第一个页的最后四个字节,其余指令放第二个页上,那么每次循环的取指时间,就是一个周期(读取第一个页内的指令)加上第二个页内指令需要 Fetch 的周期数,多的这一个周期就足以把 Fetch 宽度从后端限制中区分开,实验结果如下:

图中蓝线表示的就是上面所述的第一条指令放一个页,其余指令放第二个页的情况,可以看到每 16 条指令会多一个周期,因此 Oryon 的前端取指宽度确实是 16 条指令。

为了确认这个瓶颈是由取指造成的,又构造了一组实验,把循环的所有指令都放到一个页中,这个时候 Fetch 不再成为瓶颈(图中 aligned),两个曲线的对比可以明确地得出上述结论。

L1 ITLB

官方信息:256-entry 8-way L1 ITLB,支持 4KB 和 64KB 的页表大小

构造一系列的 B 指令,使得 B 指令分布在不同的 page 上,使得 ITLB 成为瓶颈:

可以看到 256 Page 出现了明显的拐点,对应的就是 256 的 L1 ITLB 容量。

Decode

官方信息:8 inst/cycle decoded

Return Stack

官方信息:50-entry return stack

构造不同深度的调用链,测试每次调用花费的平均时间,得到下面的图:

可以看到调用链深度为 50 时性能突然变差,因此 Return Stack 深度为 50。

Branch Predictor

官方信息:80KB Conditional Predictor, 40KB Indirect Predictor

BTB

官方信息:2K+ entry BTB

构造大量的无条件分支指令(B 指令),BTB 需要记录这些指令的目的地址,那么如果分支数量超过了 BTB 的容量,性能会出现明显下降。当把大量 B 指令紧密放置,也就是每 4 字节一条 B 指令时:

可见在 2048 个分支之内可以达到 1 的 CPI,超过 2048 个分支,出现了 3 CPI 的平台,一直延续到 32768 个分支或更多。超出 BTB 容量以后,分支预测时,无法从 BTB 中得到哪些指令是分支指令的信息,只能等到取指甚至译码后才能后知后觉地发现这是一条分支指令,这样就出现了性能损失,出现了 3 CPI 的情况。

降低分支指令的密度,在 B 指令之间插入 NOP 指令,使得每 8 个字节有一条 B 指令,得到如下结果:

可以看到 CPI=1 的拐点前移到 1024 个分支,同时 CPI=3 的平台也出现了新的拐点,在 16384 和 32768 之间。拐点的前移,意味着 BTB 采用了组相连的结构,当 B 指令的 PC 的部分低位总是为 0 时,组相连的 Index 可能无法取到所有的 Set,导致表现出来的 BTB 容量只有部分 Set,例如此处容量减半,说明只有一半的 Set 被用到了。

出现新的拐点,对应的是指令 footprint 超出 L1 ICache 的情况:L1 ICache 是 192KB,按照每 8 字节一个 B 指令计算,最多可以存放 24576 条 B 指令,这个值正好处在 16384 和 32768 之间,和拐点吻合。

小结:BTB 容量为 2048 项,采用组相连方式,当所有分支命中 BTB 时,可以达到 1 CPI;如果超出了 BTB 容量,但没有超出 L1 ICache 容量,可以达到 3 CPI。

Branch Mispredict Latency

官方信息:13 cycle Branch Mispredict Latency

后端

物理寄存器堆

官方信息:400+ registers Integer pool, 400+ registers Vector pool

为了测试物理寄存器堆的大小,一般会用两个依赖链很长的操作放在开头和结尾,中间填入若干个无关的指令,并且用这些指令来耗费物理寄存器堆。测试结果见下图:

  • 32b/64b int:测试 32/64 位整数寄存器的数量,拐点在 362-374
  • fp:测试浮点寄存器的数量,拐点在 362-372
  • flags:测试 NZCV 寄存器的数量,拐点在 119-126

可见整数和浮点数都能提供大约 360+ 个寄存器用于乱序执行,加上用于保存架构寄存器的至少 32 个寄存器,加起来和高通宣称的 400+ 是比较一致的。整数和浮点个数测出来一样,可能是这两个寄存器堆大小一样,也可能是整数和浮点放同一个寄存器堆中。经过混合整数和浮点指令测试,认为这两个寄存器堆并不共享。

NZCV 重命名则比整数寄存器少得多,只有 120+,也是考虑到 ARMv8 指令集大部分指令不像 X86 那样会修改 NZCV。

Reservation Stations

官方信息:

  • IXU 6-wide 64-bit, each with 20 entry queue
  • VXU 4-wide 128-bit, each with 48 entry queue
  • LSU 4-wide 128-bit, each with 16 entry queue (注:Hot Chips 上的 Slides 写的是四个 64-entry,出现了不一致)

执行单元

官方信息:

  • Up to 6 ALU/cycle
  • Up to 2 Branch/cycle
  • Up to 2 multiply/MLA per cycle

在循环中重复下列指令多次,测量 CPI,得到如下结果:

  • add x0, x0, 1:CPI = 6.0,说明可以 6 ALU/cycle
  • cbnz xzr, target;target::CPI = 2.0,说明可以 2 Branch/cycle,注意这里是 not taken 分支
  • mul x0, x1, x2:CPI = 2.0,说明可以 2 Multiply/cycle

Reorder Buffer

官方信息:

  • Retirement 8 uOps/cycle
  • Reorder Buffer is 650+ uOps

为了测试 ROB 的大小,设计了一个循环,循环开始是 8 条串行的 fsqrt 指令,每条指令需要 13 个周期,由于数据依赖,一共需要 8*13=104 个周期完成。之后是若干条 NOP 指令,当 NOP 指令比较少时,循环的时候取决于 fsqrt 指令的时间,一次循环大约需要 104 个周期;当 NOP 指令数量过多,填满了 ROB 以后,就会导致 ROB 无法保存下一次循环的 fsqrt 指令,性能出现下降。测试结果如下:

当 NOP 数量达到 676 时,性能开始急剧下滑,而执行 676 条 NOP 只需要 676/8=84.5 个周期,小于 104 个周期,说明瓶颈不在执行 NOP 上,而是因为 ROB 被填满,导致后续的 fsqrt 指令无法及时执行。因此认为 Oryon 的 ROB 大小在 680+。

没有观察到类似 Firestorm 的 Coalesced ROB 的设计。

Load Store Unit + L1 DCache

官方信息:

  • 96KB 6-way L1 DCache
  • 224-entry 7-way L1 DTLB, supports 4KB and 64KB translation granules
  • Up to 4 Load-Store operations per cycle
  • 192 entry Load Queue, 56 entry Store Queue
  • Full 64B/cycle for both fills and evictions to L2 cache

构造不同大小 footprint 的 pointer chasing 链,测试不同 footprint 下每条 load 指令耗费的时间:

可以看到 96KB 出现了明显的拐点,对应的就是 96KB 的 L1 DCache 容量。

用类似的方法测试 L1 DTLB 容量,只不过这次 pointer chasing 链的指针分布在不同的 page 上,使得 DTLB 成为瓶颈:

可以看到 224 Page 出现了明显的拐点,对应的就是 224 的 L1 DTLB 容量。从每个 page 一个指针改成每 32 page 一个指针并注意对齐尽量保证 Index 为 0,此时 L1 DTLB 容量降为 7,说明 L1 DTLB 是 7 路组相连结构,这些页被映射到了相同的 Set 当中:

命中 L1 DTLB 时每条 Load 指令是 3 cycle,意味着高通实现了 3 cycle 的 pointer chasing load to use latency,这个特性在苹果,Exynos M-series 和 Intel 的 E-core 中也可以看到,针对这个优化的讨论,详见 浅谈乱序执行 CPU(二:访存) 的 Load Pipeline 小节。在其他场景下,依然是 4 cycle 的 load to use latency。

针对 Load Store 带宽,实测每个周期可以完成:

  • 4x 128b Load
  • 3x 128b Load + 1x 128b Store
  • 2x 128b Load + 2x 128b Store
  • 1x 128b Load + 2x 128b Store
  • 2x 128b Store

如果把每条指令的访存位宽从 128b 改成 256b,带宽不变,指令吞吐减半。

也就是说最大的读带宽是 64B/cyc,最大的写带宽是 32B/cyc,二者不能同时达到。

MMU

官方信息:

  • 4KB and 64KB translation granules
  • 1 cycle access for L1 ITLB & L1 DTLB
  • Unified L2 TLB, 8-way >8K entry

L2 Cache

官方信息:

  • 12MB 12-way L2 Cache
  • MOESI
  • 17 cycle latency for L1 miss to L2 hit

Memory

官方信息:

  • 6MB System Level Cache, 26-29ns latency, 135GB/s bandwidth in each direction
  • LPDDR5x DRAM, 8448 MT/s, 8 channel of 16 bits, 135GB/s bandwidth, 102-104ns latency

评论