【问题标题】:What are the 128-bit to 512-bit registers used for?128 位到 512 位的寄存器是干什么用的?
【发布时间】:2019-03-26 17:12:37
【问题描述】:

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

经过一番挖掘后,我收集到的是,您必须使用 2 个 64 位运算才能对 128 位数字执行数学运算,而不是使用通用 addsub、@ 987654324@、div 操作。如果是这种情况,那么拥有这些扩展的寄存器集究竟有什么用,是否有任何汇编操作可以用来操作它们?

【问题讨论】:

  • 您链接的 wiki 文章已损坏编辑:问题是 URL 末尾有一个点。
  • 对不起。第二次尝试:这些寄存器用于Streaming SIMD Extensions

标签: assembly x86-64 sse simd cpu-registers


【解决方案1】:

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

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

举个例子,假设一个程序想要做一个双精度浮点数的矩阵乘法。使用 AVX 寄存器 ymm0ymm15,一次可以处理 4 个数字,与正常实现相比,算法速度提高了 4 倍。差别很大。

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

【讨论】:

    【解决方案2】:

    这些用于

    现在它们也常用于


    您必须使用 2 个 64 位运算才能对 128 位数字执行数学运算

    不,它们不是为此目的而设计的,您不能轻易地将它们用于 128 位数字。添加一个只有 2 条指令的 128 位数字要快得多:add rax, rbx; adc rdx, rcx,而不是处理 XMM 寄存器时的大量指令。见


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

    事实上,还有一组 8 个 80 位 ST(x) 寄存器可用于 x87 co-processor 进行浮点数学运算。然而,它们速度慢且难以预测。慢,因为默认情况下操作以更高的精度完成,这本质上需要更多的工作,如果需要,还需要requires a store then load to round to lower precision。不可预知也是因为精度高。起初可能会觉得奇怪,但很容易解释,例如 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 的原因之一


    然后英特尔为SIMD 操作引入了MMX instruction set,它使用相同的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) 扩展 SIMD 指令集时,英特尔决定为它们提供一套全新的名为 XMM 的寄存器 长了两倍(128 位),所以现在我们可以一次对 16 个字节进行操作。它还同时支持多个浮点运算。然后 Intel 在Advanced Vector Extensions (AVX) 中将 XMM 加长为 256 位 YMM,并在 AVX-512 中再次将长度加倍(这一次它还在 64 位模式下将寄存器数量增加到了 32 个)。现在您可以一次处理16 个 32 位整数

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

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

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


    1一些例子:

    【讨论】:

    • 将有负载的 x87 代码与没有负载的 SSE 代码进行比较有点不公平。在 x87 寄存器中传递浮点参数的调用约定并非不可想象。
    • 我只是想演示使用的不同指令,例如 faddp、fmul vs adds、mulss。这与内存负载无关
    • 那你为什么要包含内存负载?表面上看,您似乎想演示使用 x87 FPU 需要多少指令来计算它,而实际上并非如此。
    • 我只是复制编译器的输出。谁说是我写的?我只想要一个带有不同指令和不同寄存器的简单示例,因为 OP 说他在反汇编高级代码时从未见过这些,它们就在那里
    • @phuciv 我可以看到,但是您要提出的观点(x87 寄存器较慢)是不正确的;例如,在 Sandy Bridge 上,x87 浮点指令的延迟和吞吐量与 SSE 指令的数据完全相同。
    猜你喜欢
    • 2011-01-14
    • 2017-10-16
    • 1970-01-01
    • 2017-06-08
    • 2011-01-20
    • 2011-11-04
    • 2012-09-19
    • 1970-01-01
    • 2021-01-16
    相关资源
    最近更新 更多