Ken*_*Y-N 4 optimization sse simd auto-vectorization
在这里和互联网上,我可以找到很多关于现代编译器在许多实际情况下击败SSE的帖子,我刚刚在一些代码中遇到过我在2006年为基于整数的图像处理编写的一些SSE代码并将代码强制降低标准C分支,运行速度更快.
在具有多核和高级流水线等的现代处理器上,较旧的SSE代码是否表现不佳gcc -O2?
您必须小心使用微基准测试.这真的很容易测量比你以为你是以外的东西.就L1 I-cache/uop-cache和branch-predictor条目的压力而言,Microbenchmarks通常根本不考虑代码大小.
在大多数情况下,微基准测试通常会预测所有分支,尽管它们可以被预测,而常规调用但不紧密循环的例程在实践中可能效果不佳.
多年来,SSE已经有很多新增功能.新代码的合理基线是SSSE3(可在Intel Core2及更高版本中找到,以及AMD Bulldozer及更高版本),只要有标量回退即可.添加一个快速的byte-shuffle(pshufb)对某些事情来说是一个游戏改变者.SSE4.1也为整数代码添加了许多不错的东西.如果旧代码不使用它,编译器输出或新的手写代码可以做得更好.
目前我们需要AVX2,它可以同时处理两个128b通道,256b寄存器.有一些256b shuffle指令.AVX/AVX2提供了所有先前SSE指令的3操作数(非破坏性dest,src1,src2)版本,这有助于提高代码密度,即使使用256b操作的双通道方面是缺点(或者在没有指向AVX1的情况下) AVX2表示整数代码).
在一两年内,第一台AVX512桌面硬件可能会出现问题.这增加了大量强大的功能(屏蔽寄存器,并填充了高度非正交SSE/AVX指令集中的更多间隙),以及更宽的寄存器和执行单元.
如果旧的SSE代码在写入时仅在标量代码上提供了边际加速,或者没有人对其进行基准测试,那可能就是问题所在.编译器的进步可能导致标量C的生成代码击败旧的SSE,这需要大量的改组.有时,将数据混洗到向量寄存器中的成本会降低所有加速的速度.
或者,根据您的编译器选项,编译器甚至可能是自动向量化.IIRC,gcc -O2没有启用-ftree-vectorize,所以你需要-O3auto-vec.
另一件可能阻碍旧SSE代码的事情是它可能假设未对齐的加载/存储很慢,并且使用palignr或类似的技术在寄存器中的未对齐数据和对齐的加载/存储之间进行.所以旧的代码可能会以一种在最近的方式上实际上较慢的方式进行调整.
因此,即使不使用之前没有的任何指令,调整不同的微体系结构也很重要.
编译器输出很少是最优的,尤其是 如果你没有告诉它有关指针没有别名(restrict),或正在对齐.但它经常设法运行得非常快.你可以经常改进它(特别是通过减少uops/insns来做同样的工作,更加超线程友好),但你必须知道你所针对的微体系结构.例如,英特尔Sandybridge和更高版本只能通过单寄存器寻址模式对存储器操作数进行微熔合.x86 wiki上的其他链接.
因此,要回答标题,SSE指令集绝不是多余的或不鼓励的.不建议随意使用asm,以便随意使用(改为使用内在函数).除非您实际上可以通过编译器输出获得加速,否则不鼓励使用内在函数.如果它们现在并列,那么未来的编译器使用标量代码更容易做得比使用矢量内在函数更好.
只是为了补充Peter已经很好的答案,需要考虑的一个基本点是编译器不知道程序员对问题域的所有知识,并且程序员通常没有简单的方法来表达有用的约束和其他相关信息.一个真正聪明的编译器可能能够利用以帮助矢量化.在许多情况下,这可以为程序员提供巨大的优势.
例如,对于一个简单的情况,例如:
// add two arrays of floats
float a[N], b[N], c[N];
for (int i = 0; i < N; ++i)
a[i] = b[i] + c[i];
Run Code Online (Sandbox Code Playgroud)
任何体面的编译器都应该能够用SSE/AVX /其他方式做一个相当好的矢量化,并且用SIMD内在函数实现这一点没什么意义.除了数据对齐等相对较小的问题,或者N的可能值范围之外,编译器生成的代码应该接近最优.
但是如果你有一些不太直白的东西,比如说
// map array of 4 bit values to 8 bit values using a LUT
const uint8_t LUT[16] = { 0, 1, 3, 7, 11, 15, 20, 27, ..., 255 };
uint8_t in[N]; // 4 bit input values
uint8_t out[N]; // 8 bit output values
for (int i = 0; i < N; ++i)
out[i] = LUT[in[i]];
Run Code Online (Sandbox Code Playgroud)
您将看不到编译器的任何自动向量化,因为(a)它不知道您可以PSHUFB用来实现一个小LUT,(b)即使它确实如此,也无法知道输入数据被限制在4位范围内.因此,程序员可以编写一个简单的SSE实现,这种实现很可能会快一个数量级:
__m128i vLUT = _mm_loadu_si128((__m128i *)LUT);
for (int i = 0; i < N; i += 16)
{
__m128i va = _mm_loadu_si128((__m128i *)&b[i]);
__m128i vb = _mm_shuffle_epi8(va, vLUT);
_mm_storeu_si128((__m128i *)&a[i], vb);
}
Run Code Online (Sandbox Code Playgroud)
也许在另外10年内,编译器将足够聪明地做这种事情,并且编程语言将有方法来表达程序员对问题,数据和其他相关约束的所有知识,此时它可能是时候了像我这样的人要考虑新的职业.但在此之前,将继续存在一个大问题空间,人类仍然可以通过手动SIMD优化轻松击败编译器.