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 位数字执行数学运算,而不是使用通用 add
,sub
、mul
、div
操作。如果是这种情况,那么拥有这些扩展的寄存器组到底有什么用,您是否可以使用任何汇编操作来操纵它们?
这些寄存器是 SSE、AVX 和 AVX512 指令集扩展的一部分。您的 C 编译器应该至少使用它们的低 64 位进行浮动操作,因为 ABI 中指定了这种操作。
这些寄存器是SIMD(单指令多数据)寄存器,主要用于高性能代码。该处理器支持特殊的 SIMD 指令,可以同时处理多个数据,花费的时间与处理单个数据通常所需的时间一样多。大多数使用这些寄存器的代码都是用汇编语言或使用特殊的 内部函数 编写的,因为编译器本身不太擅长使用 SIMD 指令。让编译器在这方面做得更好(称为 自动矢量化 的优化)是一个活跃的研究领域。
举个例子,假设一个程序想要对双精度浮点数进行矩阵乘法。使用 AVX 寄存器 ymm0
到 ymm15
,一次可以处理 4 个数字,与正常实现相比,算法速度提高了 4 倍。真是天壤之别。
有关使用这些寄存器的指令,请参阅指令集参考。 This website 以易于访问的方式列出所有这些。如果你想使用它们,我建议你使用内部函数,因为它们比汇编更容易使用。
这些用于
- Floating-point 操作
- multiple pieces of data at once 上的操作(如数组或矩阵)
如今它们也常用于
- 大量数据移动(如
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++ 中有 float
或 double
,那么它们很可能存储在 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)
寄存器。不可预测也是因为精度高。一开始可能会觉得奇怪,但很容易解释,例如某些操作在 float
或 double
精度下溢出或下溢,但在 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 eXtension 或 Matrix 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
在查看 x86/x64 架构中的 table 寄存器后,我注意到有一整段 128、256 和 512 位寄存器,我从未见过它们用于汇编,或反编译 C/C++ 代码:XMM(0-15) 用于 128,YMM(0-15) 用于 256,ZMM(0-31) 512.
在做了一些挖掘之后,我收集到的是,您必须使用 2 个 64 位运算才能对 128 位数字执行数学运算,而不是使用通用 add
,sub
、mul
、div
操作。如果是这种情况,那么拥有这些扩展的寄存器组到底有什么用,您是否可以使用任何汇编操作来操纵它们?
这些寄存器是 SSE、AVX 和 AVX512 指令集扩展的一部分。您的 C 编译器应该至少使用它们的低 64 位进行浮动操作,因为 ABI 中指定了这种操作。
这些寄存器是SIMD(单指令多数据)寄存器,主要用于高性能代码。该处理器支持特殊的 SIMD 指令,可以同时处理多个数据,花费的时间与处理单个数据通常所需的时间一样多。大多数使用这些寄存器的代码都是用汇编语言或使用特殊的 内部函数 编写的,因为编译器本身不太擅长使用 SIMD 指令。让编译器在这方面做得更好(称为 自动矢量化 的优化)是一个活跃的研究领域。
举个例子,假设一个程序想要对双精度浮点数进行矩阵乘法。使用 AVX 寄存器 ymm0
到 ymm15
,一次可以处理 4 个数字,与正常实现相比,算法速度提高了 4 倍。真是天壤之别。
有关使用这些寄存器的指令,请参阅指令集参考。 This website 以易于访问的方式列出所有这些。如果你想使用它们,我建议你使用内部函数,因为它们比汇编更容易使用。
这些用于
- Floating-point 操作
- multiple pieces of data at once 上的操作(如数组或矩阵)
如今它们也常用于
- 大量数据移动(如
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++ 中有 float
或 double
,那么它们很可能存储在 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)
寄存器。不可预测也是因为精度高。一开始可能会觉得奇怪,但很容易解释,例如某些操作在 float
或 double
精度下溢出或下溢,但在 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 eXtension 或 Matrix 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