大多数函数的整数参数不超过 6 个,所以这确实是一个极端情况。在 xmm 寄存器中传递一些多余的整数参数会使在哪里找到浮点参数的规则更加复杂,几乎没有好处。除了它可能不会让代码更快的事实。
在内存中存储多余参数的另一个原因是您的函数可能不会立即使用它们。如果你想调用另一个函数,你必须将这些参数从 xmm 寄存器保存到内存中,因为你调用的函数会破坏任何参数传递寄存器。 (无论如何,所有 xmm regs 都是调用者保存的。)因此,您最终可能会得到将参数填充到无法直接使用的向量寄存器中的代码,然后在调用另一个函数之前将它们存储到内存中,并且仅然后 将它们加载回整数寄存器。或者即使该函数不调用其他函数,也许它需要向量寄存器供自己使用,并且必须将参数存储到内存中以释放它们以运行向量代码!将push 参数放到堆栈上会更容易,因为push 非常优化,出于显而易见的原因,在单个微指令中完成存储和RSP 的修改,与mov 一样便宜.
SysV Linux/Mac x86-64 ABI (r11) 中有一个整数寄存器不用于参数传递,但也不保留调用。为惰性动态链接器代码使用临时寄存器而不保存(因为此类 shim 函数需要将其所有 args 传递给动态加载的函数)和类似的包装器函数是很有用的。
所以 AMD64 可以为函数参数使用更多的整数寄存器,但代价是调用函数在使用前必须保存的寄存器数量。 (或者用于不使用“静态链”指针或其他东西的语言的双用途 r10。)
无论如何,在寄存器中传递的参数越多并不总是越好。
xmm 寄存器不能用作指针或索引寄存器,将数据从 xmm 寄存器移回整数寄存器可能会比加载刚刚存储的数据更减慢周围的代码。 (如果任何执行资源将成为瓶颈,而不是缓存未命中或分支错误预测,则更有可能是 ALU 执行单元,而不是加载/存储单元。将数据从 xmm 移动到 gp 寄存器需要 ALU uop,在 Intel和 AMD 当前的设计。)
L1 缓存非常快,并且存储->负载转发使往返内存的总延迟大约为 5 个周期,例如英特尔哈斯韦尔。 (像inc dword [mem]这样的指令的延迟是6个周期,包括一个ALU周期。)
如果将数据从 xmm 移动到 gp 寄存器是所有您将要做的(没有其他东西可以让 ALU 执行单元保持忙碌),那么是的,在 Intel CPU 上,@ 的往返延迟987654329@/movd eax, xmm0(2 个周期 Intel Haswell)小于 mov [mem], eax/mov eax, [mem](5 个周期 Intel Haswell)的延迟,但整数代码通常不会像 FP 代码那样完全受到延迟的限制。
在 AMD Bulldozer 系列 CPU 上,两个整数内核共享一个向量/FP 单元,直接在 GP regs 和 vector regs 之间移动数据实际上非常慢(单向 8 或 10 个周期,或 Steamroller 的一半)。一次内存往返只有 8 个周期。
32 位代码设法运行得相当好,即使 所有 参数都在堆栈上传递,并且必须加载。 CPU 在将参数存储到堆栈然后再次加载它们方面进行了高度优化,因为笨拙的旧 32 位 ABI 仍然用于很多代码,尤其是。在 Windows 上。 (大多数 Linux 系统大多运行 64 位代码,而大多数 Windows 桌面系统运行大量 32 位代码,因为很多 Windows 程序只能作为预编译的 32 位二进制文件提供。)
请参阅http://agner.org/optimize/ 获取 CPU 微体系结构指南,了解如何计算实际需要多少个周期。 x86 wiki 中还有其他很好的链接,包括上面链接的 x86-64 ABI 文档。