如何让GCC使用SSE指令对这个简单的复制循环进行矢量化?

时间:2013-03-25 18:16:33

标签: c gcc sse vectorization compiler-optimization

这是关于让GCC在循环中优化memcpy()的{​​{3}}的后续行动;我已经放弃并决定采用手动优化循环的直接途径。

尽管如此,我仍然希望保持可移植性和可维护性,因此我希望GCC能够在不使用SSE内在函数的情况下对一个简单优化的循环复制进行矢量化。但是,无论手动向量化版本(使用SSE2 MOVDQA指令)对于小型阵列(<32),实际上高达58%的速度,它似乎拒绝这样做,无论我给它多少手持量。元素)并且对于较大的元素(> = 512)至少快17%。

这是未手动矢量化的版本(提供尽可能多的提示,告诉GCC对其进行矢量化):

__attribute__ ((noinline))
void take(double * out, double * in,
          int stride_out_0, int stride_out_1,
          int stride_in_0, int stride_in_1,
          int * indexer, int n, int k)
{
    int i, idx, j, l;
    double * __restrict__ subout __attribute__ ((aligned (16)));
    double * __restrict__ subin __attribute__ ((aligned (16)));
    assert(stride_out_1 == 1);
    assert(stride_out_1 == stride_in_1);
    l = k - (k % 8);
    for(i = 0; i < n; ++i) {
        idx = indexer[i];
        subout = &out[i * stride_out_0];
        subin = &in[idx * stride_in_0];
        for(j = 0; j < l; j += 8) {
            subout[j+0] = subin[j+0];
            subout[j+1] = subin[j+1];
            subout[j+2] = subin[j+2];
            subout[j+3] = subin[j+3];
            subout[j+4] = subin[j+4];
            subout[j+5] = subin[j+5];
            subout[j+6] = subin[j+6];
            subout[j+7] = subin[j+7];
        }
        for( ; j < k; ++j)
            subout[j] = subin[j];
    }
}

这是我第一次尝试手动矢量化,我用它来比较性能(它肯定可以进一步改进,但我只是想测试最天真的转换):

__attribute__ ((noinline))
void take(double * out, double * in,
          int stride_out_0, int stride_out_1,
          int stride_in_0, int stride_in_1,
          int * indexer, int n, int k)
{
    int i, idx, j, l;
    __m128i * __restrict__ subout1 __attribute__ ((aligned (16)));
    __m128i * __restrict__ subin1 __attribute__ ((aligned (16)));
    double * __restrict__ subout2 __attribute__ ((aligned (16)));
    double * __restrict__ subin2 __attribute__ ((aligned (16)));
    assert(stride_out_1 == 1);
    assert(stride_out_1 == stride_in_1);
    l = (k - (k % 8)) / 2;
    for(i = 0; i < n; ++i) {
        idx = indexer[i];
        subout1 = (__m128i*)&out[i * stride_out_0];
        subin1 = (__m128i*)&in[idx * stride_in_0];
        for(j = 0; j < l; j += 4) {
            subout1[j+0] = subin1[j+0];
            subout1[j+1] = subin1[j+1];
            subout1[j+2] = subin1[j+2];
            subout1[j+3] = subin1[j+3];
        }
        j *= 2;
        subout2 = &out[i * stride_out_0];
        subin2 = &in[idx * stride_in_0];
        for( ; j < k; ++j)
            subout2[j] = subin2[j];
    }
}

(处理某些特殊情况的实际代码稍微复杂一些,但不会影响GCC矢量化,因为即使上面给出的版本也没有矢量化:我的测试工具可以在{{3上找到}})

我正在使用以下命令行编译第一个版本:

gcc-4.7 -O3 -ftree-vectorizer-verbose=3 -march=pentium4m -fverbose-asm \
    -msse -msse2 -msse3 take.c -DTAKE5 -S -o take5.s

用于主复制循环的结果指令始终是FLDL / FSTPL对(即以8字节为单位复制)而不是MOVDQA指令,这在我使用SSE时会产生内在手动。

tree-vectorize-verbose的相关输出似乎是:

  

分析循环at take.c:168

  168:vect_model_store_cost:硬件支持的未对齐   168:vect_model_store_cost:inside_cost = 8,outside_cost = 0。
  168:vect_model_load_cost:硬件支持的未对齐   168:vect_model_load_cost:inside_cost = 8,outside_cost = 0   168:成本模型:增加循环版本化别名检查的成本。

  168:成本模型:epilogue剥离iters设置为vf / 2,因为循环迭代是未知的   168:成本模型:向量迭代成本= 16除以标量迭代成本= 16大于或等于向量化因子= 1。   168:没有矢量化:矢量化无利可图。

我不确定为什么它指的是“未对齐”的存储和负载,并且在任何情况下问题似乎都不能证明矢量化是有利可图的(尽管根据经验,它适用于所有情况问题,我不确定它不会是什么情况。)

我在这里缺少任何简单的旗帜或提示,或者GCC不管怎样都不想这样做?

如果这是显而易见的话,我会感到尴尬,但希望这也可以帮助其他人,如果是的话。

1 个答案:

答案 0 :(得分:4)

所有__attribute__ ((aligned (16)))指令实现的很少,因为它们只是定义指针​​变量本身的对齐方式,而不是指针所指向的数据。

您可能需要查看__builtiin_assume_aligned