4

这是关于让 GCC在循环中优化的问题的后续内容;memcpy()我已经放弃并决定直接手动优化循环。

不过,我试图尽可能地保持便携性和可维护性,所以我想让 GCC 在不诉诸 SSE 内在函数的情况下对一个简单优化的重复循环内复制本身进行矢量化。然而,无论我给它多少手,它似乎都拒绝这样做,尽管手动矢量化版本(使用 SSE2MOVDQA指令)在经验上对于小数组(<32 个元素)和至少 17较大的(> = 512)更快。

这是未手动矢量化的版本(有尽可能多的提示告诉 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 矢量化,因为即使上面给出的版本也不矢量化:我的测试工具可以在LiveWorkspace上找到)

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

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似乎是:

在 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:成本模型:结语剥离迭代器设置为 vf/2 因为循环迭代是未知的。
168:成本模型:向量迭代成本=16除以标量迭代成本=16大于等于向量化因子=1。
168:未向量化:向量化不盈利。

我不确定为什么它指的是“未对齐”的存储和加载,无论如何,问题似乎是无法证明矢量化是有利可图的(即使从经验上看,它适用于所有情况,并且我不确定在什么情况下不会)。

是否有任何我在这里遗漏的简单标志或提示,或者 GCC 无论如何都不想这样做?

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

4

1 回答 1

4

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

您可能需要查看__builtiin_assume_aligned

于 2013-03-25T18:31:34.973 回答