128位到512位寄存器有什么用?

What are the 128-bit to 512-bit registers used for?

在查看 x86/x64 架构中的 table 寄存器后,我注意到有一整段 128、256 和 512 位寄存器,我从未见过它们用于汇编,或反编译 C/C++ 代码:XMM(0-15) 用于 128,YMM(0-15) 用于 256,ZMM(0-31) 512.

在做了一些挖掘之后,我收集到的是,您必须使用 2 个 64 位运算才能对 128 位数字执行数学运算,而不是使用通用 addsubmuldiv 操作。如果是这种情况,那么拥有这些扩展的寄存器组到底有什么用,您是否可以使用任何汇编操作来操纵它们?

这些寄存器是 SSE、AVX 和 AVX512 指令集扩展的一部分。您的 C 编译器应该至少使用它们的低 64 位进行浮动操作,因为 ABI 中指定了这种操作。

这些寄存器是SIMD(单指令多数据)寄存器,主要用于高性能代码。该处理器支持特殊的 SIMD 指令,可以同时处理多个数据,花费的时间与处理单个数据通常所需的时间一样多。大多数使用这些寄存器的代码都是用汇编语言或使用特殊的 内部函数 编写的,因为编译器本身不太擅长使用 SIMD 指令。让编译器在这方面做得更好(称为 自动矢量化 的优化)是一个活跃的研究领域。

举个例子,假设一个程序想要对双精度浮点数进行矩阵乘法。使用 AVX 寄存器 ymm0ymm15,一次可以处理 4 个数字,与正常实现相比,算法速度提高了 4 倍。真是天壤之别。

有关使用这些寄存器的指令,请参阅指令集参考。 This website 以易于访问的方式列出所有这些。如果你想使用它们,我建议你使用内部函数,因为它们比汇编更容易使用。

这些用于

如今它们也常用于

  • 大量数据移动(如 memcpy),因为它们可以在迭代中复制大量数据:
    • Why are memcpy() and memmove() faster than pointer increments?
    • Does rewriting memcpy/memcmp/... with SIMD instructions make sense?
    • faster alternative to memcpy?
    • Why is memcpy() faster?
  • 字符串操作(避免逐个字符迭代):
    • Can I use SIMD for speeding up string manipulation?

you have to use 2 64-bit operations in order to perform math on a 128-bit number

不,它们不是用于此目的,您不能轻易将它们用于 128 位数字。如果处理 XMM 寄存器,仅使用 2 条指令添加 128 位数字要快得多:add rax, rbx; adc rdx, rcx 而不是大量指令。参见

  • Is it possible to use SSE and SSE2 to make a 128-bit wide integer?

关于它们的用途,首先它们用于标量浮点运算。因此,如果您在 C 或 C++ 中有 floatdouble,那么它们很可能存储在 XMM 寄存器的低位部分,并由以 ss 结尾的指令操作(单标量) 或sd (双标量)

事实上,如果需要,x87 co-processor for doing floating-point math. However they're slow and less predictable. Slow because operations are done in higher precision by default, which inherently needs more work and also requires a store then load to round to lower precision 还可以使用另一组八个 80 位 ST(x) 寄存器。不可预测也是因为精度高。一开始可能会觉得奇怪,但很容易解释,例如某些操作在 floatdouble 精度下溢出或下溢,但在 long double 精度下不会。这会在 32 位和 64 位构建1

中导致许多错误或意外结果

Here is a floating-point example on both sets of registers

// f = x/z + y*z
x87:
        fld     dword ptr [esp + 12]
        fld     st(0)
        fdivr   dword ptr [esp + 4]
        fxch    st(1)
        fmul    dword ptr [esp + 8]
        faddp   st(1)
        ret
SSE:
        divss   xmm0, xmm2
        mulss   xmm1, xmm2
        addss   xmm0, xmm1
        ret
AVX:
        vdivss  xmm0, xmm0, xmm2
        vmulss  xmm1, xmm1, xmm2
        vaddss  xmm0, xmm0, xmm1
        ret

转向更快、更一致的 SSE 寄存器是 the 80-bit extended precision long double type is not available in MSVC anymore

的原因之一

然后 Intel 引入了 MMX instruction set for SIMD 操作,它使用相同的 ST(x) 寄存器和新名称 MMX。 MMX 可能代表 Multiple Math eXtensionMatrix Math eXtension,但恕我直言,它很可能是 MultiMedia eXtension ,因为多媒体和互联网在当时变得越来越重要。在多媒体解决方案中,您经常需要对每个像素、纹素、声音样本执行相同的操作......就像这些

for (int i = 0; i < 100000; ++i)
{
   A[i] = B[i] + C[i];
   D[i] = E[i] * F[i];
}

与其分别对每个元素进行操作,我们可以通过一次对多个元素进行操作来加快速度。这就是人们发明 SIMD 的原因。使用 MMX,您可以同时增加 8 个像素通道的亮度,或一次增加四个 16 位声音样本的音量...对单个元素的操作称为 scalar,而整个寄存器称为向量,即一组标量值

由于MMX的缺点(如ST寄存器的重用,或缺少浮点支持),当用Streaming SIMD Extensions (SSE) Intel decided to give them a completely new set of registers named XMM which is twice longer (128 bits), so now we can operate on 16 bytes at once. And it also supports multiple floating-point operations at once. Then Intel lengthened XMM to the 256-bit YMM in Advanced Vector Extensions (AVX), and doubled the length once again in AVX-512扩展SIMD指令集时(这次也增加了在 64 位模式下,寄存器数量增加到 32 个)。现在您可以一次处理 16 个 32 位整数

从上面你可能理解了这些寄存器的第二个也是最重要的作用:用一条指令并行地对多个数据进行操作。比如在SSE4 a set of instructions to work on C strings中就有介绍。现在您可以计算字符串长度、查找子字符串……通过一次检查多个字节来更快。您还可以更快地复制或比较内存。现代 memcpy 实现一次移动 16、32 或 64 个字节,具体取决于最大寄存器宽度,而不是像最简单的 C 解决方案那样逐个移动。

不幸的是,编译器在从标量代码转换为并行代码方面仍然很糟糕,所以大多数时候我们不得不帮助他们,尽管自动矢量化仍然变得更好更智能

由于 SIMD 的重要性,如今几乎所有高性能架构都有自己的 SIMD 版本,例如 Altivec on PowerPC or Neon/SVE 在 ARM 上。


1一些例子:

  • Is SSE floating-point arithmetic reproducible?
  • Extended (80-bit) double floating point in x87, not SSE2 - we don't miss it?
  • Why would the same code yield different numeric results on 32 vs 64-bit machines?
  • Difference in floating point arithmetics between x86 and x64
  • std::pow produce different result in 32 bit and 64 bit application
  • Why does Math.Exp give different results between 32-bit and 64-bit, with same input, same hardware