为什么不在XMM向量寄存器中存储函数参数?

den*_*631 15 x86 assembly x86-64 parameter-passing calling-convention

我正在阅读这本书:"计算机系统 - 程序员视角".我发现,在x86-64架构中,我们仅限于6个积分参数,这些参数将被传递给寄存器中的函数.下一个参数将在堆栈上传递.

而且,第一个最多8个FP或矢量args以xmm0..7传递.

为什么不使用浮点寄存器来存储下一个参数,即使参数不是单/双精度变量?

将数据存储在寄存器中比将其存储到存储器然后从存储器中读取它会更有效(据我所知).

Pet*_*des 20

大多数函数没有超过6个整数参数,所以这实际上是一个极端情况.在xmm寄存器中传递一些多余的整数参数将使得找到浮点算法的位置的规则更加复杂,几乎没有任何好处.除了它可能不会使代码更快的事实.

将多余参数存储在内存中的另一个原因是您可能无法立即使用它们.如果要调用另一个函数,则必须将这些参数从xmm寄存器保存到内存,因为您调用的函数将破坏任何参数传递寄存器.(并且所有的xmm regs都是调用者保存的.)所以你可能最终得到的代码将参数填充到向量寄存器中,它们不能直接使用,并且在调用另一个函数之前将它们存储到内存中,并且只有然后将它们加载回整数寄存器.或者即使该函数没有调用其他函数,也许它需要向量寄存器供自己使用,并且必须将params存储到内存中以释放它们以运行向量代码!这本来是容易只是pushPARAMS到堆栈,因为push非常大量优化,出于显而易见的原因,做存储和可吸入悬浮粒子的修改都在一个单一UOP,大约便宜,因为一个mov.

有一个整数寄存器不用于参数传递,但在SysV Linux/Mac x86-64 ABI(r11)中也没有调用保留.在没有保存的情况下使用临时动态链接器代码的临时寄存器很有用(因为这样的填充函数需要将所有的args传递给动态加载的函数),以及类似的包装器函数.

因此,AMD64可能会使用更多的整数寄存器作为函数参数,但这只会以调用函数在使用前必须保存的寄存器数量为代价.(或者两用r10用于不使用"静态链"指针的语言.)

无论如何,在寄存器中传递的更多参数并不总是更好.


xmm寄存器不能用作指针或索引寄存器,将xmm寄存器中的数据移回整数寄存器可能会比加载刚刚存储的数据更慢.(如果任何执行资源将成为瓶颈,而不是缓存未命中或分支错误预测,则更有可能是ALU执行单元,而不是加载/存储单元.将数据从xmm移动到gp寄存器需要ALU uop,在Intel中和AMD目前的设计.)

L1缓存非常快,并且存储 - >负载转发使得到内存的往返总延迟达到例如Intel Haswell的5个周期.(类似指令的延迟inc dword [mem]是6个周期,包括一个ALU周期.)

如果从XMM将数据移动到GP寄存器是所有你要做的事情(不加任何东西,以保持ALU执行单元忙),然后是,Intel CPU的往返时延为movd xmm0, eax /movd eax, xmm0(2次英特尔的Haswell)小于延迟mov [mem], eax/ mov eax, [mem](5个周期Intel Haswell),但整数代码通常不会像FP代码那样通过延迟完全陷入瓶颈.

在AMD Bulldozer系列CPU中,两个整数内核共享一个向量/ FP单元,直接在GP regs和向量regs之间移动数据实际上非常慢(单向8或10个周期,或者是Steamroller的一半).内存往返仅为8个周期.

32位代码设法运行得相当好,即使所有参数都在堆栈上传递,并且必须加载.CPU经过高度优化,可以将参数存储到堆栈中,然后再次加载它们,因为古老的32位ABI仍然用于大量代码,尤其是.在Windows上.(大多数Linux系统主要运行64位代码,而大多数Windows桌面系统运行大量32位代码,因为许多Windows程序仅作为预编译的32位二进制文​​件提供.)

有关CPU微体系结构指南,请参阅http://agner.org/optimize/,了解如何确定实际需要多少循环. wiki中还有其他很好的链接,包括上面链接的x86-64 ABI文档.

  • @ denis631 - 这是一个很好的答案,它解释了通过寄存器与来电/被叫节省的成本之间的平衡/压力.我在本问答中没有看到任何对它的引用,但您可能希望查看[ABI文档](http://www.x86-64.org/documentation.html). (2认同)