16.4 SSE 和 AVX 扩展

SIMD 指令集是 SSE 和 AVX 扩展的基础。大部分 SIMD 指令都用来操作多个数据对儿;例如,mulps 可以一次性同时对四对 32 位浮点数进行乘法运算。不过现在在进行浮点数运算时,更推荐单对的操作指令(例如 mulss)。

默认情况下,GCC 会生成 SSE 指令来操作浮点数。这些指令可以接收 xmm 寄存器或者内存中的内容作为操作数。


■Consistency 简略起见,我们略过了历史遗留的浮点数专用栈的说明,不过还是需要指出,所有程序部件被翻译的时候都应该使用同一种浮点运算:要么使用浮点数栈,要么使用 SSE 指令集。


我们从列表 16-30 中的例子开始吧。

Listing 16-30.simd_main.c

#include <stdlib.h>
#include <stdio.h>

void sse( float[static 4], float[static 4] );

int main() {
    float x[4] = {1.0f, 2.0f, 3.0f, 4.0f };
    float y[4] = {5.0f, 6.0f, 7.0f, 8.0f };

    sse( x, y );

    printf( "%f %f %f %f\n", x[0], x[1], x[2], x[3] );
    return 0;
}

这个地方使用了一个别的地方定义的函数 sse,接收两个 float 类型的数组作为参数。每一个参数都需要至少四个元素。该函数将会执行计算,并修改第一个数组。

我们称多个值为 packed 当这些值能够依次填充满同一个 xmm 寄存器。在列表 16-30 中,float x[4] 就是四个单精度浮点数 packed 的值。

在列表 16-31 中我们以汇编形式定义一个 sse 函数。

Listing 16-31.simd_asm.asm

section .text
global sse

; rdi = x, rsi = y

sse:
    movdqa xmm0, [rdi]
    mulps  xmm0, [rsi]
    addps  xmm0, [rsi]
    movdqa [rdi], xmm0
    ret

这个文件定义了一个 sse 函数。该函数执行了四种 SSE 指令:

  • movdqa(MOVe Double Qword Aligned) 从 rdi 寄存器指向的内存位置拷贝 16 个字节到 xmm0 寄存器。我们在 14.1.1 节中见过这条指令。

  • mulps(MULtiply Packed Single precision floating point values) 从 rsi 指向的内存地址中取出数据,并对 xmm0 寄存器进行连续四次浮点数运算。

  • addps(ADD Packed Singled precision floating point) 从 rsi 指向的内存地址中取数据,并进行四次连续的浮点数计算。

  • movdqa 从 xmm0 寄存器中拷贝数据到 rdi 指向的内存地址。

换句话说,四对浮点数会被进行乘法运算,然后每一对的第二个浮点数会再被加到第一个浮点数上。

命名方面的规则倒是比较通用:动作元语(mov,add,mul...)加上后缀。第一个后缀可以是 P(packed) 或者 S(scalar 表示单个值)。第二个后缀可以是 D 表示双精度值(C 语言里的 double) 或者 S 表示单精度值(C 语言里的 float)。

这里再强调一遍,大多数 SSE 指令只接收进行过内存对齐的内存操作数。

为了完成课后作业,你需要查阅 Intel 软件开发手册[15] 来学习下面这些指令:

  • movsd–Move Scalar Double-Precision Floating- Point Value.
  • movdqa–Move Aligned Double Quad word.
  • movdqu–Move Unaligned Double Quad word.
  • mulps–Multiply Packed Single-Precision Floating Point Values.
  • mulpd–Multiply Packed Double-Precision Floating Point Values.
  • addps–Add Packed Single-Precision Floating Point Values.
  • haddps–Packed Single-FP Horizontal Add.
  • shufps–Shuffle Packed Single-Precision Floating Point Values.
  • unpcklps–Unpack and Interleave High Packed Double-Precision Floating Point Values.
  • packswb–Pack with Signed Saturation.
  • cvtdq2pd–Convert Packed Dword Integers to Packed Double-Precision FP Values.

这些指令是 SSE 扩展的一部分。Intel 引入了名为 AVX 的新扩展,该扩展使用了 ymm0,ymm1...ymm15 等新寄存器。这些寄存器为 256 位宽度,其低 128 位可以当作原来的 xmm 寄存器来使用。

新指令大多数有字母 v 作为前缀,例如 vbroadcastss

了解你的 CPU 是否支持 AVX 很重要,并不是说 AVX 指令一定比 SSE 快!同一产品家族中的处理器在指令集上没有区别,只在集成电路数量上有差别。便宜的处理器一般 ALU 数量会少一些。

结合 mulps 指令和 ymm 寄存器,可以做到同时对 8 对浮点数做乘法。

较好的 CPU 在 ALU 数量上足以同时将八对浮点数同时进行乘法运算。便宜的 CPU 可能只有四个 ALU,所以需要在微指令执行两次操作,每次乘 4 对。程序员一般会注意到使用指令时,可能语义上完全一致,但性能方面却大不相同。AVX 版本的 mulps 和 ymm 寄存器以及 8 对浮点数有时候甚至也可以比 SSE 版本的 mulps 和 xmm 寄存器结合 4 对浮点数运算两次还要慢!

results matching ""

    No results matching ""