suk*_*rst 2 performance 64-bit assembly x86-64 micro-optimization
当 8 位指令和 64 位 x64/Amd64 处理器上的 64 位指令除了位宽之外相似/相同时,这些指令之间是否存在执行时序差异?有没有办法找到执行这两个微小汇编函数的真实处理器时序?
-谢谢。
; 64 bit instructions
add64:
mov $0x1, %rax
add $0x2, %rax
ret
; 8 bit instructions
add8:
mov $0x1, %al
add $0x2, %al
ret
Run Code Online (Sandbox Code Playgroud)
是的,有区别。 mov $0x1, %al对大多数 CPU 上的 RAX 旧值有错误的依赖,包括所有比 Sandybridge 新的 CPU。这是一条2输入1输出指令;从 CPU 的角度来看,它就像add $1, %al独立调度它或与 RAX 的其他用途无关。仅写入 32 位或 64 位寄存器才会启动新的依赖链。
这意味着函数的 AL 返回值add8可能要等到调用者在调用之前恰好在 EAX 中执行的某些独立工作发生缓存未命中之后才准备好,但 RAX 结果add64可能会立即准备好,以应对无序情况执行以开始执行调用者中使用返回值的后续指令。(假设他们的其他输入也准备好了。)
它们的代码大小也不同:两个 8 位指令都是 2 个字节长。(感谢 AL,imm8 短格式编码;add $1, %dl将是 3 个字节)。RAX 指令的长度为 7 和 4 字节。这对于 L1i 缓存占用空间很重要(并且在大规模上,对于必须从磁盘调入多少字节)。在小规模上,如果 CPU 正在执行传统解码,因为代码在 uop 缓存中还不是热的,则可以在 16 或 32 字节读取块中容纳多少条指令。此外,后面指令的代码对齐会受到前面指令长度变化的影响,有时会影响哪些分支彼此别名。
https://agner.org/optimize/解释了各种 x86 微架构的管道细节,包括前端解码效果,它可以使指令长度的影响超出 I-cache / uop-cache 中的代码密度。
一般来说,32 位操作数大小是最有效的(对于性能而言,对于代码大小来说也相当不错)。32 和 8 是 x86-64 可以使用的操作数大小,无需额外的前缀,在实践中,使用 8 位来避免停顿和不良,您需要更多指令或更长的指令,因为它们不进行零扩展。 在 x86-64 中使用 32 位寄存器/指令的优点。
对于 64 位操作数大小,一些指令实际上在 ALU 中较慢,而不仅仅是前端效应。这包括div大多数 CPU 和imul一些较旧的 CPU。还有 popcnt 和 bswap。例如,试除代码在 Windows 上的 32 位运行速度比 Linux 上的 64 位运行速度快 2 倍
请注意,mov $0x1, %rax将使用 GAS 组装为 7 个字节,除非您使用as -O2(与 不同gcc -O2,请参阅此示例)来使其优化到mov $1, %eax完全相同的架构效果,但更短(没有 REX 或 ModRM 字节)。有些汇编器默认会进行这种优化,但 GAS 不会。 为什么 Linux 上的 NASM 更改 x86_64 汇编中的寄存器更多地说明了为什么这种优化是安全且良好的,以及为什么您应该在源代码中自己执行此操作,特别是如果您的汇编器不为您执行此操作。
但除了 false dep 和代码大小之外,它们对于 CPU 后端是相同的:所有这些指令都是单微指令,并且可以在任何标量整数 ALU 执行端口1上运行。 (https://uops.info/对每条非特权指令的每种形式都有自动测试结果)。
脚注 1:Excavator(最新一代推土机系列)还可以mov $imm, %reg在 2 个以上端口 (AGU) 上运行,实现 32 位和 64 位操作数大小。但是将新的低 8 或低 16 合并到完整寄存器中需要 ALU 端口。因此,mov $1, %raxExcavator 上的吞吐量为 4/时钟,但mov $1, %al只有 2/时钟吞吐量。(当然,只有当您使用几个不同的目标寄存器,而不是实际上重复使用 AL 时;这将是 1/时钟的延迟瓶颈,因为在该微架构上写入部分寄存器存在错误依赖性。)
从 Piledriver 开始的以前的 Bulldozer 系列 CPU 可以mov reg, reg在 EX0、EX1、AGU0、AGU1 上运行(对于 r32 或 r64),而大多数 ALU 指令(包括mov $imm, %reg只能在 EX0/1 上运行)。进一步扩展 AGU 端口的功能以处理 mov-immediate 是 Excavator 中的一项新功能。
幸运的是,Bulldozer 已被 AMD 更好的 Zen 架构所取代,该架构具有 4 个全标量整数 ALU 端口/执行单元。(更宽的前端和 uop 缓存,良好的缓存,并且通常不会像 Bulldozer 那样糟糕。)
是的,但通常不在使用 . 调用的函数中call。相反,将其放入展开的循环中,以便您可以使用最少的其他指令多次运行它。对于查看 CPU 性能计数器结果以查找前端/后端 uop 计数以及循环的总时间特别有用。
您可以构建循环来测量延迟或吞吐量;请参阅 NASM 中的 RDTSCP 始终返回相同的值(对单个指令进行计时)。还:
一般来说,您不需要测量自己(尽管了解如何测量很好,这可以帮助您了解测量的真正含义)。人们已经对大多数 CPU 微架构做到了这一点。您可以根据分析指令来预测特定 CPU 对于某些循环的性能(如果您可以假设没有停顿或缓存未命中)。通常,这可以相当准确地预测性能,但是 OoO exec 只能部分隐藏的中等长度的依赖链使得准确预测或解释每个周期变得非常困难。
| 归档时间: |
|
| 查看次数: |
489 次 |
| 最近记录: |