x86 组装pushad/popad,速度有多快?

2024-02-29

我只是想在 x86 汇编中制作非常快速的基于计算的程序 但我需要在调用程序之前推送累加器、计数器和数据寄存器。手动推送它们更快:

push eax
push ecx
push edx

或者只是使用,

pushad

和 pop 一样。谢谢


如果你关心性能,pusha / popa几乎没有用处。它们仅在以牺牲速度为代价来优化代码大小时才有用,例如保存/恢复函数周围的寄存器。但对于非人来说非常不方便void函数,因为它们会重新加载all寄存器,因此您必须将返回值存储在内存中(例如,通过将被加载到的堆栈槽)eax,或其他地方之后重新加载popad).

只压入需要保存的寄存器,或者您想要作为函数参数传递。或者,在内联汇编 /questions/tagged/inline-assembly,只需让编译器通过声明来为您管理寄存器"=r"(dummy1)任何临时寄存器的虚拟输出操作数,或在特定寄存器上使用 clobber。通常,编译器可以选择可以让您破坏而不保存的寄存器。 (或者在笨重的 MSVC 风格的内联汇编中,编译器无法为您分配寄存器,因此您必须手动选择。编译器会解析您的汇编以查找破坏者。)

您通常不需要保存/恢复eax;为了性能你应该mov esi, eax/调用/使用中的值esi,如果您无法计算其中的值esi首先。即使用调用保留寄存器来保存需要保存的值call,因此重要值的存储/重新加载不在关键路径上。相反,存储/重新加载位于您(或编译器)调用者的调用保留寄存器之一的关键路径上push/pop围绕整个函数,在任何循环之外。

查看更多关于调用保留寄存器与调用破坏寄存器 https://stackoverflow.com/questions/9268586/what-are-callee-and-caller-saved-registers/56178078#56178078以及保存/恢复通常如何进行。以及什么是良好的调用约定,例如x86-64 System V 是如何设计的 https://stackoverflow.com/questions/4429398/why-does-windows64-use-a-different-calling-convention-from-all-other-oses-on-x86/35619528#35619528,并且本次问答 https://stackoverflow.com/questions/33707228/why-not-store-function-parameters-in-xmm-vector-registers关于应该在寄存器中传递多少个参数,以及为什么不使用 XMM 寄存器来传递整数参数。当然,辅助函数可以使用自定义调用约定。


pusha / popa在大多数 CPU 上都很慢

即使您确实想推送所有 8 个整数寄存器(包括esp!),使用8个独立的push现代 CPU 上的指令实际上更快。 Pusha/popa 是微编码的,这对于前端来说可能是一个问题 https://stackoverflow.com/questions/26907523/branch-alignment-for-loops-involving-micro-coded-instructions-on-intel-snb-famil。 (尽管 8 个单字节指令也可能对 uop 缓存造成问题。但在实际代码中,您通常只需要推送几个寄存器,而不是全部。)

如果您正在针对过时的 CPU(例如原始的有序 Pentium 和 Pentium II/III)进行优化,则 Pusha/popa 的速度可达 8push r or 8 pop r,实际上更少的微指令,因为他们没有堆栈引擎来消除 ESP 更新微指令。

From Agner Fog 的说明书 http://agner.org/optimize/:现代 CPU 具有单微指令push reg and pop reg,因为编译器始终使用这些指令,因此对性能很重要。推送/弹出吞吐量通常与存储/加载吞吐量相匹配(通常每个时钟 1 次存储或每个时钟 2 次加载)。但pusha / popa编译器不使用它们,因此 CPU 设计者没有特殊的支持来提高它们的速度。popa吞吐量仅限于每个时钟 1 个负载,如果just跑步popa。 (我认为在 Intel CPU 上,测量性能最可能的解释是popa不使用堆栈引擎,因此它的瓶颈在于对esp.)

Intel:

  • 天湖:pusha:11 uop,8c 吞吐量。popa:18 uops / 8c 吞吐量。
  • 珊迪大桥:pusha:16 uops / 8c 吞吐量。popa:18 uops / 9c 吞吐量。
  • 尼哈勒姆:pusha:18 uops / 8c 吞吐量。popa:10 uops / 8c 吞吐量。
  • 西尔弗蒙特/KNL:pusha:10 uops / 10c 吞吐量。popa:17 uops / 14c 吞吐量。
  • 奔腾4:pusha:4/10 uops / 19c 吞吐量。popa:4/16 uops / 14c 吞吐量。
  • P5 Pentium 1 / MMX:5-9 个周期,不可配对。 “如果 SP 能被 4 整除,则为 9(不完美配对)。”

AMD: pusha/popa在某些 AMD CPU 上表现出奇的好,尤其是 K8。

  • Ryzen: pusha:9 uop,8c 吞吐量。popa: 9 个微指令,4c 吞吐量。 (与英特尔不同的是,AMD 的新设计popa不低于8倍pop.)
  • Jaguar: pusha:9 uops / 8c 吞吐量。popa:9 uops / 8c 吞吐量。 (Jaguar 通常每个时钟只能执行一次负载。)
  • 打桩机:pusha:9 uops / 9c 吞吐量。popa:14 uops / 8c 吞吐量。 (阿格纳列出了常规pop regBulldozer 系列的吞吐量为每个时钟 1,尽管我认为他们确实有一个堆栈引擎并且每个时钟可以执行 2 个负载。也许堆栈引擎一次只能处理一条堆栈指令?)
  • K8: pusha:9 uops / 4c 吞吐量!! (不知道这是怎么可能的,要么这是表中的错误或拼写错误,要么 K8 合并了 32 位寄存器并进行了四个 64 位存储)。popa:9 uops / 4c 吞吐量。这些数字看起来确实是真实的:InstLatx86 测量 http://users.atw.hu/instlatx64/AuthenticAMD0000F4A_K8_Clawhammer_InstLatX86.txt同意 4c 吞吐量pushad / popadClawhammer(第一代 K8 微架构)。很明显AMD在优化上付出了一些努力pushad.

您标记了此内联汇编 /questions/tagged/inline-assembly。通常你应该避免使用call在内联汇编中,因此 C 编译器知道该调用。

让编译器关心寄存器;只需告诉它你修改了哪些(GNU Casm("..." ::: "eax", "ecx")或其他),或者在 MSVC 风格的内联汇编中,它会解析您的汇编并知道写入了哪些寄存器。如果其中包含任何调用保留的寄存器,编译器将在整个函数的开始/结束处保存/恢复这些寄存器,即使 asm 语句处于循环中也是如此。 (它可能需要在asm语句或块之前/之后溢出和/或重新加载一些本地变量,但会使用mov,而不是push/pop。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

x86 组装pushad/popad,速度有多快? 的相关文章

随机推荐