Gcc 手动矢量化代码比自动优化慢10倍-我做错了什么?

Gcc 手动矢量化代码比自动优化慢10倍-我做错了什么?,gcc,optimization,scipy,vectorization,Gcc,Optimization,Scipy,Vectorization,我试图学习如何利用gcc的矢量化。我遵循了本教程(带) 我只是把它改成了两倍。我使用这个点积来计算随机生成的1200x1200双精度方阵(300x300双精度方阵4)的乘法。我检查结果是否相同。但真正让我吃惊的是,这个简单的点积实际上比我手动矢量化的速度快了10倍 也许,double4对于SSE来说太大了(它需要AVX2?),但我希望,即使gcc无法立即找到处理double4的合适指令,它仍然能够利用数据在大数据块中的显式信息进行自动矢量化 详细信息: double dot_simple(

我试图学习如何利用gcc的矢量化。我遵循了本教程(带)

我只是把它改成了两倍。我使用这个点积来计算随机生成的1200x1200双精度方阵(300x300双精度方阵4)的乘法。我检查结果是否相同。但真正让我吃惊的是,这个简单的点积实际上比我手动矢量化的速度快了10倍

也许,double4对于SSE来说太大了(它需要AVX2?),但我希望,即使gcc无法立即找到处理double4的合适指令,它仍然能够利用数据在大数据块中的显式信息进行自动矢量化


详细信息:

double dot_simple(  int n, double *a, double *b ){
    double dot = 0;
    for (int i=0; i<n; i++){ 
        dot += a[i]*b[i];
    }
    return dot;
}
结果是:

dot_simple:
time elapsed 1.90000 [s] for 1.728000e+09 evaluations => 9.094737e+08 [ops/s]

dot_SSE:
time elapsed 15.78000 [s] for 1.728000e+09 evaluations => 1.095057e+08 [ops/s]
我在Intel®Core上使用了gcc 4.6.3™ i5 CPU 750@2.67GHz×4,带这些选项
-std=c99-O3-ftree矢量化-展开循环-参数max unroll times=4-ffast math
或仅带
-O2
(结果是一样的)

为了方便起见,我使用python/scipy.weave()实现了这一点,但我希望它不会改变任何事情

代码:

double dot_simple(  int n, double *a, double *b ){
    double dot = 0;
    for (int i=0; i<n; i++){ 
        dot += a[i]*b[i];
    }
    return dot;
}

其中一个主要问题是,在函数
dot_SSE
中,当您只应循环n/2项(或使用AVX循环n/4项)时,您循环n项

要使用GCC的向量扩展解决此问题,可以执行以下操作:

double dot_double2(int n, double *a, double *b ) {
    typedef double double2 __attribute__ ((vector_size (16)));
    double2 sum2 = {};
    int i;
    double2* a2 = (double2*)a;
    double2* b2 = (double2*)b;
    for(i=0; i<n/2; i++) {
        sum2 += a2[i]*b2[i];
    }
    double dot = sum2[0] + sum2[1];
    for(i*=2;i<n; i++) dot +=a[i]*b[i]; 
    return dot;
}
如果使用FMA编译此文件,它将生成FMA3指令。我在这里测试了所有这些函数(您也可以自己编辑和编译代码)


请注意,在矩阵乘法中使用SSE/AVX生成单点并不是SIMD的最佳用途。对于双浮点,您应该使用SSE(AVX)一次进行两(四)个点积。

您的
double4
类型没有映射到SSE向量类型。尝试使用例如
double2
,其中每个向量有两个double2,然后应该映射到
\uuum128d
。另外,请确保编译器标志中有
-msse
(或者更好的
-msse4
,因为您的目标是Core i5)。好的,谢谢,现在我得到了一个小的(20%)改进:1.22 ops/s,不需要手动矢量化,1.46+09 ops/s,需要手动矢量化。我希望gcc能够聪明地将double4自动拆分为2 double2,如果它不合适的话。尽管如此,平面循环的自动矢量化似乎很聪明,如果你真的想做手动优化,那么你应该考虑使用本质,而不是非标准的GCC语法糖——对于这样一个简单的操作,尽管你可能发现很难用任何显著的余量来打败自动向量化。除非您有相当丰富的SSE工作经验。您好,谢谢您的回答。我试试看。我想澄清一下,my
dot_SSE()
中的
int n
不是数组中的双倍数,而是向量数(已除以
VECTOR_SIZE
),如
mmul()
中所示。您测试过我的函数了吗?什么是加速?
def mmul_2(A, B, C, __force__=0 ):
    code = r'''     mmul( NA[0]/4, A, B, C );            '''
    weave_options = {
    'extra_compile_args': ['-std=c99 -O3 -ftree-vectorize -unroll-loops --param max-unroll-times=4 -ffast-math'],
    'compiler' : 'gcc', 'force' : __force__ }
    return weave.inline(code, ['A','B','C'], verbose=3, headers=['"vectortest.h"'],include_dirs=['.'], **weave_options )
double dot_double2(int n, double *a, double *b ) {
    typedef double double2 __attribute__ ((vector_size (16)));
    double2 sum2 = {};
    int i;
    double2* a2 = (double2*)a;
    double2* b2 = (double2*)b;
    for(i=0; i<n/2; i++) {
        sum2 += a2[i]*b2[i];
    }
    double dot = sum2[0] + sum2[1];
    for(i*=2;i<n; i++) dot +=a[i]*b[i]; 
    return dot;
}
double dot_double2_unroll2(int n, double *a, double *b ) {
    typedef double double2 __attribute__ ((vector_size (16)));
    double2 sum2_v1 = {};
    double2 sum2_v2 = {};
    int i;
    double2* a2 = (double2*)a;
    double2* b2 = (double2*)b;
    for(i=0; i<n/4; i++) {       
        sum2_v1 += a2[2*i+0]*b2[2*i+0];
        sum2_v2 += a2[2*i+1]*b2[2*i+1];
    }
    double dot = sum2_v1[0] + sum2_v1[1] + sum2_v2[0] + sum2_v2[1];
    for(i*=4;i<n; i++) dot +=a[i]*b[i]; 
    return dot;
}
double dot_double4(int n, double *a, double *b ) {
    typedef double double4 __attribute__ ((vector_size (32)));
    double4 sum4 = {};
    int i;
    double4* a4 = (double4*)a;
    double4* b4 = (double4*)b;
    for(i=0; i<n/4; i++) {       
        sum4 += a4[i]*b4[i];
    }
    double dot = sum4[0] + sum4[1] + sum4[2] + sum4[3];
    for(i*=4;i<n; i++) dot +=a[i]*b[i]; 
    return dot;
}