SSE指令MOVSD(扩展: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代码,人们可能会认为这个问题也很模糊。 在这里,我将提供一个回答作为答案,希望对任何不清楚现代CPU上的浮点运算的人都有用。

现代CPU上浮点标量/向量处理的综述

矢量处理的概念可追溯到旧时矢量处理器 ,但这些处理器已被具有缓存系统的现代架构所取代。 所以我们专注于现代CPU,尤其是x86和x86-64 。 这些架构是高性能科学计算的主流 。

从i386开始,英特尔推出了浮点堆栈,其中可以保存高达80位宽的浮点数。 该堆栈通常称为x87或387浮点“寄存器” ,带有一组x87 FPU指令 。 x87堆栈不是真正的,可直接寻址的寄存器,如通用寄存器,因为它们位于堆栈中。 访问寄存器st(i)是通过偏移堆栈顶部寄存器%st(0)或简单地%st。 借助于在当前堆栈顶部%st和一些偏移寄存器%st(i)之间交换内容的指令FXCH,可以实现随机访问。 但FXCH可以施加一些性能损失,尽管最小化。 x87堆栈通过默认情况下以80位精度计算中间结果来提供高精度计算,以最小化数值不稳定算法中的舍入误差。 但是,x87指令是完全标量的。

矢量化的第一个努力是MMX指令集 ,它实现了整数向量运算。 MMX下的向量寄存器是64位宽寄存器MMX0,MMX1,…,MMX7。 每个都可用于以“打包”格式保存64位整数或多个较小整数。 然后可以将单个指令同时应用于两个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的情况下随机访问。 当我发布我的问题时,我显然没有意识到这一事实。 我不清楚的另一个概念是通用寄存器是整数/地址寄存器。 即使它们在x86-64上是64位,它们也不能保持64位浮点。 主要原因是与通用寄存器相关联的执行单元是ALU(算术和逻辑单元),它不用于浮点计算。

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

 -mfpmath=387 

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

 -mfpmath=sse 

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

 -msse2 

那么XMM寄存器将是唯一可以进行计算的地方。 换句话说,这个标志打开-mfpmath = sse。 有关更多信息,请参阅GCC的x86,x86-64配置 。 有关编写SSE2 C代码的示例,请参阅我的其他文章如何让GCC完全展开此循环(即剥离此循环)? 。

SSE指令集虽然非常有用,但并不是最新的矢量扩展。 AVX, 高级矢量扩展通过提供3个操作数和4个操作数指令来增强SSE。 如果您不清楚这意味着什么,请参阅指令集中的操作数数量 。 3操作数指令通过1)使用少1个寄存器来优化科学计算中常见的融合乘加(FMA)运算; 2)减少寄存器之间明确的数据移动量; 3)加速FMA计算本身。 例如使用AVX,请参阅@Nominal Animal对我post的回答 。