SSE 指令 MOVSD(扩展:x86、x86-64 上的浮点标量和向量运算)

SSE instruction MOVSD (extended: floating point scalar & vector operations on x86, x86-64)

我对 MOVSD 汇编指令有些困惑。我写了一些计算一些矩阵乘法的数字代码,只是使用没有 SSE 内在函数的普通 C 代码。我什至没有包含用于编译的 SSE2 内部函数的头文件。但是当我检查汇编器输出时,我看到:

1)使用128位向量寄存器XMM; 2) SSE2指令MOVSD被调用。

我知道 MOVSD 本质上是在单双精度浮点数上运行。它只使用 XMM 寄存器的低 64 位并将高 64 位设置为 0。但我只是不明白两件事:

1) 我从不给编译器任何使用 SSE2 的提示。另外,我使用的是 GCC 而不是英特尔编译器。据我所知,intel编译器会自动寻找向量化的机会,但GCC不会。那么 GCC 是怎么知道使用 MOVSD 的呢??或者,这条 x86 指令是否早在 SSE 指令集之前就已存在,而 SSE2 中的 _mm_load_sd() 内在函数只是为了向后兼容使用 XMM 寄存器进行标量计算?

2) 为什么编译器不使用其他浮点寄存器,要么是80位浮点栈,要么是64位浮点寄存器??为什么必须使用 XMM 寄存器(通过设置高 64 位 0 并实质上浪费该存储空间)来收费? XMM 是否提供更快的访问速度?


顺便问一下,我还有一个关于 SSE2 的问题。我只是看不出 _mm_store_sd() 和 _mm_storel_sd() 之间的区别。两者都将低 64 位值存储到一个地址。有什么不同?性能差异??对齐差异??

谢谢。


更新 1:

好吧,很明显,当我第一次问这个问题时,我缺乏一些关于 CPU 如何管理浮点运算的基本知识。所以专家们倾向于认为我的问题是无意义的。由于我什至没有包括最短的示例 C 代码,因此人们可能也会认为这个问题含糊不清。在这里,我将提供 作为答案,希望这对任何不清楚现代 CPUs.

浮点运算的人都有用。

现代 CPU 上的浮点 scalar/vector 处理回顾

矢量处理的思想可以追溯到很久以前 vector processors, but these processors had been superseded by modern architectures with cache systems. So we focus on modern CPUs, especially x86 and x86-64. These architectures are the main stream in high performance scientific computing

自 i386 以来,Intel 引入了浮点堆栈,其中可以保存高达 80 位宽的浮点数。这个堆栈通常被称为 x87 or 387 floating point "registers", with a set of x87 FPU instructions。 x87 堆栈不像通用寄存器那样是真正的、可直接寻址的寄存器,因为它们在堆栈上。访问寄存器 st(i) 是通过偏移堆栈顶部寄存器 %st(0) 或简单地 %st。借助交换当前堆栈顶部 %st 和某个偏移寄存器 %st(i) 之间的内容的 FXCH 指令,可以实现随机访问。但是 FXCH 可以施加一些性能损失,尽管最小化。 x87 堆栈通过默认以 80 位精度计算中间结果来提供高精度计算,以最大限度地减少数值不稳定算法中的舍入误差。但是,x87 指令完全是标量。

矢量化的第一个尝试是 MMX instruction set,它实现了 整数 向量运算。 MMX下的向量寄存器是64位宽的寄存器MMX0,MMX1,...,MMX7。每个都可用于保存 64 位整数,或 "packed" 格式的多个较小的整数。然后可以将一条指令同时应用于两个 32 位整数、四个 16 位整数或八个 8 位整数。所以现在有用于标量整数运算的传统通用寄存器,以及用于没有共享执行资源的整数向量运算的新 MMX。但是 MMX 与标量 x87 FPU 操作共享执行资源:每个 MMX 寄存器对应于 x87 寄存器的低 64 位,x87 寄存器的高 16 位未使用。这些 MMX 寄存器均可以直接寻址。但是别名使得在同一应用程序中处理浮点和整数向量运算变得困难。为了最大限度地提高性能,程序员通常只在一种模式或另一种模式下使用处理器,尽可能长时间地推迟它们之间相对较慢的切换。

后来,SSE 在 x87 堆栈的一侧创建了一组单独的 128 位宽寄存器 XMM0–XMM7。 SSE 指令专门 专注于单精度浮点运算(32 位);整数向量运算仍然使用 MMX 寄存器和 MMX 指令集执行。但现在这两个操作可以同时进行,因为它们不共享执行资源。 要知道SSE不仅可以做浮点向量运算,还可以做浮点标量运算。本质上它提供了一个新的浮动操作发生的地方,x87堆栈不再是执行浮动操作的优先选择。使用 XMM 寄存器进行标量浮点运算比使用 x87 堆栈更快,因为所有 XMM 寄存器都更容易访问,而 x87 堆栈不能在没有 FXCH 的情况下随机访问。 当我发布问题时,我是显然不知道这个事实。另一个我不清楚的概念是通用寄存器是 integer/address 寄存器。即使它们在 x86-64 上是 64 位的,它们也不能容纳 64 位浮点数。主要原因是通用寄存器关联的执行单元是ALU(arithmetic & logical unit),不是浮点运算。

SSE2 是一个重大进步,因为它扩展了向量数据类型,因此 SSE2 指令,无论是标量还是向量,都可以使用所有 C 标准数据类型。这种扩展实际上使 MMX 过时了。此外,x87 堆栈不再像以前那么重要了。由于有两个可以进行浮点运算的替代位置,您可以向编译器指定您的选项。例如对于 GCC,使用 flag

进行编译
-mfpmath=387

将在旧版 x87 堆栈上安排浮点运算。 请注意,这似乎是 32 位 x86 的默认设置,即使 SSE 已经可用。例如,我有一台 2007 年生产的 Intel Core2Duo 笔记本电脑,它已经配备了SSE 发布到版本 SSE4,而 GCC 仍将默认使用 x87 堆栈,这使得科学计算不必要地变慢。在这种情况下,我们需要使用 flag

进行编译
-mfpmath=sse

并且 GCC 将在 XMM 寄存器上安排浮点运算。 64 位 x86-64 用户无需担心此类配置,因为这是 x86-64 上的默认设置。这样的信号只会影响标量浮点运算。如果我们使用向量指令编写代码并使用 flag

编译代码
-msse2

那么 XMM 寄存器将是唯一可以进行计算的地方。换句话说,此标志打开 -mfpmath=sse。有关详细信息,请参阅 GCC's configuration of x86, x86-64. For examples of writing SSE2 C code, see my other post

SSE 指令集虽然非常有用,但并不是最新的矢量扩展。 AVX,advanced vector extensions enhances SSE by providing 3-operands and 4 operands instructions. See number of operands in instruction set if you are unclear of what this means. 3-operands instruction optimizes the commonly seen fused multiply-add (FMA) operation in scientific computing by 1) using 1 fewer register; 2) reducing the explicit amount of data movement between registers; 3) speeding up FMA computations in itself. For example of using AVX, see .