第1章 X86 SSE/AVX指令集

SSE/AVX是Intel公司设计的、对其X86体系的SIMD扩展指令集,它基于SIMD向量化技术,提高了X86硬件的计算能力,增强了X86多核向量处理器的图像和视频处理能力。

SSE/AVX指令支持向量化数据并行,一个指令可以同时对多个数据进行操作,同时操作的数据个数由向量寄存器的长度和数据类型共同决定。例如,SSE4向量寄存器(xmm)长度为128位,即16个字节,如果操作float或int数据,可同时操作4个,如果操作char数据,可同时操作16个。而AVX向量寄存器(ymm)长度为256位,即32字节,如果操作char类型数据,可同时操作32个,潜在地大幅度提升程序性能。

虽然SSE4/AVX指令向量寄存器的长度为128/256位,但是同样支持64位长度的向量操作,64位向量映射到向量寄存器的前64位,这保证了兼容性。在64位程序下,SSE4/AVX向量寄存器的个数是16。

通常SSE指令要求访问时内存地址对齐到向量长度,主要是为了减少内存或缓存操作的次数,如果内存地址没有对齐到向量长度,则会增加访问存储器的次数。SSE4指令要求存储器地址必须16字节对齐,而AVX指令最好也32字节对齐。SSE4及以前的SSE指令不支持不对齐的读写操作,为了简化编程和扩大应用面,AVX指令集支持不对齐的读写,但是性能大约会下降20%。为了性能,在可能的情况下,还是应当使用对齐的读写操作。

SSE4/AVX加入了stream的概念,意为不使用缓存的读写,因为不使用缓存则留给其他读取操作的缓存就多些,可能会提高性能。实际上这可能是一个“搬石头砸自己脚”的技术,使用不好的话,可能会对程序性能有负面的影响。笔者并不是不建议使用它,而是让读者明白,使用它可能会适得其反,因此要特别注意。

Intel ICC和开源的GCC编译器支持的SSE/AVX指令的C接口(intrinsic,内置函数)声明在intrinsic.h头文件中。其数据类型命名主要有__m128/__m256、__m128d/__m256i,默认为单精度(d表示双精度,i表示整型)。其函数的命名可大致分为3个使用“_”隔开的部分,3个部分的含义如下。

·第一个部分为_mm或_mm256。_mm表示其为SSE指令,操作的向量长度为64位或128位。_mm256表示AVX指令,操作的向量长度为256位。本节只介绍128位的SSE指令和256位的AVX指令。

·第二个部分为操作函数名称,如_add、_load、mul等,一些函数操作会增加修饰符,如loadu表示不对齐到向量长度的存储器访问。

·第三个部分为操作的对象名及数据类型,_ps表示操作向量中所有的单精度数据;_pd表示操作向量中所有的双精度数据;_pixx表示操作向量中所有的xx位的有符号整型数据,向量寄存器长度为64位;_epixx表示操作向量中所有的xx位的有符号整型数据,向量寄存器长度为128位;_epuxx表示操作向量中所有的xx位的无符号整型数据,向量寄存器长度为128位;_ss表示只操作向量中第一个单精度数据;si128表示操作向量寄存器中的第一个128位有符号整型。

3个部分组合起来,就形成了一条向量函数,如_mm256_add_ps表示使用256位向量寄存器执行单精度浮点加法运算。

由于使用指令级数据并行,因此其粒度非常小,需要使用细粒度的并行算法设计。SSE/AVX指令集对分支的处理能力非常差,而从向量中抽取某些元素数据的代价又非常大,因此不适合含有复杂逻辑的运算。