现代编译器如何使用mmx/3dnow/sse指令?

21

我一直在研究x86指令集扩展,它们只在某些特定情况下才有用(例如SSE3中的HADDPD-水平添加打包双精度)。这些扩展需要特定的寄存器布局,需要通过特定的指令序列来设置。像gcc这样的通用编译器实际上多少会使用这些指令(或其子集),还是主要用于手写汇编语言?编译器如何检测何时适合使用SIMD指令?

5个回答

23

通常情况下,很少有编译器使用它们。GCC和Visual Studio通常不能使用SIMD指令。如果您将SSE作为编译器标志启用,它将使用标量SSE指令进行常规浮点运算,但通常不要期望自动使用向量化指令。最近的版本可能在某些情况下能够使用它们,但上次我尝试时并未生效。Intel的C ++编译器是我所知道的唯一一个能够自动矢量化某些循环的大型编译器。

总的来说,您将不得不亲自使用它们。可以使用原始汇编语言或使用编译器内置函数。一般而言,我会说内置函数是更好的方法,因为它们更容易让编译器理解代码,并进行调度和优化,但实际上,我知道至少MSVC并不总是从内置函数生成非常高效的代码,因此纯汇编可能是最好的解决方案。尝试实验,看看哪种方式有效。但是,除非您1)使用正确的编译器,2)编写相当简单的循环以便进行矢量化,否则不要期望编译器为您使用这些指令。

更新2012
好的,自我写这个答案以来已经过去三年了。几年前,GCC已经能够自动矢量化(简单的)代码,在VS2012中,MSVC 终于获得了相同的功能。当然,我的答案的主要部分仍然适用:编译器仍然只能将相当简单的代码向量化。对于任何更复杂的内容,您都必须使用内置函数或内联汇编语言。


近几年来,指令集是否变得更好了?上次我检查时,MSVC和ICC的寄存器分配都相当糟糕,即使是我手写汇编也能轻松击败编译器内置版本。 - snemarch
我相信最近的MSVC版本已经对内嵌函数生成的代码进行了一些改进。但我不知道这些改进带来了多大的差异。 - jalf
MSVC对于标量SSE的输出仍然非常糟糕,特别是在您任何地方使用内置函数时。 - Crashworks

9

4
如何自动利用SSE和其他小向量单元(无需程序员以特殊语言结构或特别编译器“内置函数”的形式指示)一直是编译器研究的话题。大多数结果似乎专门针对特定问题域,例如数字信号处理。我没有跟上这个主题的文献,但我所读的内容表明,利用向量(SSE)单元仍然是一个研究课题,并且应该对常用于该领域的通用编译器保持低期望值。
建议搜索词:向量化编译器。

2020年更新:主要的静态编译器(非JIT)可以相当可靠地将简单的“垂直”操作向量化,其中循环体访问一些具有相同索引的数组,例如A[i] = B[i] * x + C[i]或其他类型(整数或FP)。不适用于像A[idx[i]]这样的gather或scatter操作。对于具有不同类型宽度、任何重组或结构的数组,或任何更复杂的类似于串行依赖关系(例如前缀和)的东西,你仍然经常需要手动向量化才能获得最佳效果。 - Peter Cordes
有些编译器甚至可以将数学库函数如“log”或“exp”矢量化,但快速的SIMD近似计算可能会带来很大的优势,例如,如果您知道您不关心处理NaN或Inf输入,并且可以接受较低精度的输出。 - Peter Cordes

1
我曾看到gcc使用sse来清零默认的std :: string对象。这不是特别强大的sse使用,但它确实存在。然而,在大多数情况下,您将不得不编写自己的代码。
我知道这是因为我允许堆栈变得不对齐,导致程序崩溃,否则我可能没有注意到这个问题!

是的,现代针对x86-64的编译器自由地使用16字节的加载/存储来复制结构体并将其清零。 - Peter Cordes

0
如果您使用向量Pascal编译器,您将获得对于SIMD有优势的类型的高效SIMD代码。基本上,这是长度小于64位的任何内容。(对于64位实数,使用SIMD实际上更慢)。 编译器的最新版本还会自动在多个核之间并行化处理。

64位实数,也称为“double”,在任何支持SSE2的CPU上都可以受益于SIMD,除了可能会将128位向量操作拆分成两个64位半部分,并且多uop指令会导致解码瓶颈的Pentium-M / Core Solo。在Core2或AMD K10之后的任何设备上,SIMD对于“double”来说都是一个明显的优势。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接