为什么不尽可能将 128 位 SIMD 内联函数矢量化为 256 位?



假设我有这个函数:

void test32(int* a, int* b, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        a[i] = a[i] + b[i];
    }
}

Clang和gcc在使用-O3 -march=core-avx2(godbolt(编译时都产生256位SIMD。

现在假设我有这个功能:

void test128(__m128i* a, __m128i* b, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        a[i] = _mm_add_epi32(a[i], b[i]);
    }
}

对于相同的CFLAGS,clang和gcc都拒绝将其矢量化为256位(godbolt(。

因此,与手动向量化的SSE2代码相比,天真代码(自动向量化(每次迭代处理的元素数量是前者的两倍。这有什么意义?当AVX2可用时,是否有方法指示编译器将128位SIMD内部向量化为256位?

不幸的是,没有,我不知道有什么编译器选项可以将内部向量(或GNU C本机向量(重新矢量化为更广泛的类型对于容易自动向量化的情况,这是不首先手动向量化的原因之一

有时,告诉编译器你希望它使用什么矢量化策略是很有用的,而这正是内部函数的作用所在。

如果编译器过于激进地重写它们,在某些情况下会很糟糕。就像使用更宽矢量的循环后面的清理循环一样,您可以使用128位来在后面留下更少的标量元素。或者,您可能有16字节的对齐,但没有32字节,并且您特别关心Sandybridge(其中32字节的加载/存储不对齐非常糟糕(。或者你在Haswell服务器上,256位AVX可以减少最大turbo(至少对于FP数学指令(,所以你只想在程序的某些阶段运行的一些函数中使用256位矢量。

基本上,这是一种权衡,一方面是为了让聪明的人指定他们想要的东西,另一方面是给编译器一种可以理解和优化程序逻辑的方式(比如+运算符:这并不意味着你会得到一条asm-add指令(。

MSVC和ICC倾向于比GCC/clang更字面地理解内部,而不是通过它们进行持续传播。GCC/clang选择如何处理内部问题在很多方面都是明智的。

如果您有这样一个微不足道的可向量化问题(没有循环携带的依赖项或shuffle(,并且您希望您的代码可用于未来更广泛的矢量指令,请使用OpenMP #pragma omp simd告诉编译器您肯定希望它向量化如果你不启用完全优化,让编译器尝试自动向量化每个循环,那么OpenMP就是为了解决这种问题。(在gcc -O3,或clang -O2。对于GCC12,也为-O2。(OpenMP可以让编译器以没有-ffast-math通常不允许的方式对FP数学进行矢量化,例如,像数组之和这样的FP约简。

最新更新