首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我怎样才能更好地与GCC进行矢量化?

我怎样才能更好地与GCC进行矢量化?
EN

Stack Overflow用户
提问于 2016-10-09 19:20:53
回答 1查看 834关注 0票数 4

考虑执行相同计算的这三个函数:

代码语言:javascript
复制
#include <x86intrin.h>

void testfunc_loop(double a, double b, double* dst)
{
    double f[] = {a,b,-a,-b};

    for(int n = 0; n < 4; ++n)
    {
        dst[n] = 0.1 + f[n]*(1.0 + 0.5*f[n]);
    }
}

void testfunc_flat(double a, double b, double* dst)
{
    dst[0] = 0.1 + ( a)*(1.0 + 0.5*( a));
    dst[1] = 0.1 + ( b)*(1.0 + 0.5*( b));
    dst[2] = 0.1 + (-a)*(1.0 + 0.5*(-a));
    dst[3] = 0.1 + (-b)*(1.0 + 0.5*(-b));
}

void testfunc_avx(double a, double b, double* dst)
{
    __m256d one      = _mm256_set1_pd(1.0);
    __m256d half     = _mm256_set1_pd(0.5);
    __m256d tenth    = _mm256_set1_pd(0.1);

    __m256d v = _mm256_set_pd(-b,-a,b,a);

    __m256d q = _mm256_add_pd(tenth,_mm256_mul_pd(v,_mm256_add_pd(one,_mm256_mul_pd(half,v))));

    _mm256_store_pd(dst,q);
}

GCC 4.7.2 (与-O3 -mavx一起)将循环版本矢量化,但对展开循环使用标量操作。三个版本的(规范化)次数分别为3.3次(循环,自动矢量化),1.2次(展开,标量),1次(手动avx)。展开版本和手动向量化函数之间的性能差异很小,但是我想强制矢量化,因为它在完整的代码中是有益的。

使用不同编译器进行的测试(请参阅https://godbolt.org/g/HJH2CX)表明,clang自动地向化了展开循环(从3.4.1版本开始),但是GCC到版本7还没有。我能和GCC自动得到类似的矢量化吗?我只找到了与循环矢量化相关的优化选项,它们没有帮助。GCC网站显示自2011年以来没有任何新闻。

EN

回答 1

Stack Overflow用户

发布于 2016-10-09 23:30:30

gcc经常不把单矢量的东西矢量化。我在现有的代码库(无边的天空)中也看到过类似的缺乏自动矢量化的无边的天空类。

因此,如果需要将x86内联到快速代码中,则可能必须手动将其向量化。(您还可以考虑传递__m256d值,而不是存储到数组。)

顺便说一下,手动矢量化的版本可能会更快。我在“哥德波特”上玩了一遍,注意到_mm256_set_pd(-b,-a, b,a)正在编译愚蠢的代码,所以手动编写会更有效。此外,如果您没有FMA可用,您可以通过重构表达式来减少延迟。(允许0.1 -/+ a与平方平行发生)。Code+asm在这里

代码语言:javascript
复制
// 0.1 + a  + 0.5*a*a   =  0.1 +   a  * (1.0 + 0.5*a)
//     + b
// 0.1 - a  + 0.5*a*a   =  0.1 + (-a) * (1.0 - 0.5*a)
//     - b

// only one of the mul+add pairs can fuse into an FMA
// but 0.1+/-a happens in parallel with 0.5*a*a, so it's lower latency without FMA
void testfunc_latency_without_fma(double a, double b, double* dst)
{
  // 6 AVX instructions other than the store:
  // 2 shuffles, 1 mul, 1 FMA, 1 add.  1 xor.  In theory could run one iteration per 2 clocks
    __m256d abab       = _mm256_setr_pd(a, b, a, b);    // 1c + 3c latency (unpck + vinsertf128)
    __m256d sq256      = _mm256_mul_pd(abab, abab);     // 5c
    const __m256d half = _mm256_set1_pd(0.5);
    __m256d sq_half256 = _mm256_mul_pd(sq256, half);    // 5c: dependency chain 1 ready in 14c from a and b being ready

    // we could use a smaller constant if we do _mm256_setr_m128d(ab, xor(ab, set1(-0.))
    // but that takes an extra vinsertf128 and this part isn't the critical path.
    const __m256d upper_signmask = _mm256_setr_pd(0. ,0. ,-0. ,-0.);
    __m256d ab_negab = _mm256_xor_pd(abab, upper_signmask); // chain2: 1c from abab

    const __m256d tenth   = _mm256_set1_pd(0.1);
    __m256d tenth_plusminus_ab = _mm256_add_pd(tenth, ab_negab); // chain2: 3c (ready way ahead of squared result)

    __m256d result = _mm256_add_pd(tenth_plusminus_ab, sq_half256);  // fuses with the sq_half
    _mm256_store_pd(dst, result);
}

当你测试的时候,为什么自动矢量化循环速度这么慢?它将标量存储到数组中,然后是向量负载,导致一个~11循环存储转发失速。因此,它的延迟比其他两种方法中的任何一种都要高得多,但是IDK如果这会影响吞吐量的话。看看你是如何测试的;也许你是用一个调用的结果作为下一个调用的输入?或者,在相同的堆栈空间上重复存储转发的货摊是个问题?

一般来说,对于较大的数组,gcc非常喜欢指针对齐。它生成巨大的、完全展开的标量介绍/外部代码以达到对齐指针,然后使用对齐存储/加载。

这对现代CPU没有多大帮助(但通常也不会造成太大的伤害),特别是对于通常在运行时对齐的数据,但是如果数据通常是不对齐的,或者是运行在预Nehalem CPU上,则可能是好的。

如果这与gcc不愿自动矢量化小事情有关,但告诉它double*是对的,似乎没有帮助。

我认为问题的一部分是,它不太擅长插入洗牌,以矢量化的代码,需要洗牌。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39947582

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档