为什么gcc autovectorization对3x3的卷积矩阵不起作用?

Mar*_*tin 10 c x86 gcc compiler-optimization auto-vectorization

我已经为卷积矩阵实现了以下程序

#include <stdio.h>
#include <time.h>

#define NUM_LOOP 1000
#define N 128   //input or output dimention 1
#define M N     //input or output dimention 2
#define P 5 //convolution matrix dimention 1 if you want a 3x3 convolution matrix it must be 3
#define Q P     //convolution matrix dimention 2
#define Csize P*Q   
#define Cdiv  1     //div for filter 
#define Coffset 0   //offset 

//functions
void unusual(); //unusual implementation of convolution
void naive();
//data
unsigned short int input[N][M] __attribute__(( aligned(32))); // input data
unsigned short int output[N][M] __attribute__(( aligned(32))); // out put data
unsigned short int kernel[P][Q] __attribute__(( aligned(32)));//convolution coefficients

int main(){
    struct timespec tStart, tEnd;//used to record the processiing time
    double tTotal , tBest=10000;//minimum of toltal time will asign to the best time

    int w=0;
    do{// this loop repeat the body to record the best time
        clock_gettime(CLOCK_MONOTONIC,&tStart);

        //function to be executed here :

        unusual();

        clock_gettime(CLOCK_MONOTONIC,&tEnd);
        tTotal = (tEnd.tv_sec - tStart.tv_sec);
        tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;

        if(tTotal<tBest)
            tBest=tTotal;
    } while(w++ < NUM_LOOP);

    printf(" The best time: %lf sec in %d repetition for %dX%d matrix\n",tBest,w, MAX1, MAX2);

    return 0;
}

//unusual sequential convolution
void unusual(){
    int i, j,k,temp;

    for (i=P/2; i< N-P/2; i++){
        for(j=Q/2; j< M-Q/2; j++){
            temp=0;
            for(k=0; k< Csize; k++){
                temp += (kernel[k/P][k%Q]) * (input[i - (P/2) + (k/Q)][j - (Q/2) + (k%Q)]);

            }
            output[i][j]=((temp/(Cdiv))+Coffset);
        }
    }
}
//The naive implementation
inline void naive(){
    int i, j,k,l,temp;
    for (i=P/2; i< N-P/2; i++){
        for(j=Q/2; j< M-Q/2; j++){
            temp=0;

            for(k = 0; k <  P; k++){ 
                for(l = 0; l <  Q; l++){
                    temp += (kernel[k][l]) * (input[i - (P/2)+k][j - (Q/2)+l]);
                }
            }
            output[i][j]=((temp/(Cdiv))+Coffset);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是当我-O3用于自动矢量化时,它只适用于3x3卷积矩阵.我已经看到汇编输出和自动矢量化只是对3x3内核进行了一些更改并合理地提高了性能(20倍快速注意:异常函数的标量版本比天真的乐趣慢)但是5x5卷积矩阵没有改进

更新:我在问题中加入了朴素的实现,并将图片大小更改为NxM,将矩阵转换为内核,将Cdim1xCdim2更改为PxQ,将seqConv函数更改为异常以便澄清.问题不是改善异常功能的实施.问题是虽然所有元素都在内存的相同位置,但gcc使用启发式等等,为什么gcc无法改进这种不寻常的实现? 注意:问题不在于天真的实现.gcc -O3通过~7加速提高3x3,5x5内核的天真实现.它也可以通过~1.5加速来实现7x7和9x9.为了改善卷积,我使用内在函数和加速比原始实现快40倍以上,比异常卷积快约2倍.所以我的矢量化比我不常见的快80倍.手调整优化不是问题.自动矢量化器优化是问题,并且失败的原因.

GCC命令: gcc -Wall -march=native -O3 -o "%e" "%f"

平台:Linux mint,Skylake,gcc 6.2

提前致谢

vgu*_*nic 2

我的猜测是,由于内存对齐问题,它无法优化。您已指定卷积为 2 字节 Short。大多数 SSE 函数喜欢使用 128 位向量,而 AVX 喜欢使用 512 位向量。

在我的机器上我声明了这样的转换:

uint16_t conv[Cdim1][8] = {0}; //You need to pad extra fields with zeroes
Run Code Online (Sandbox Code Playgroud)

然后像这样替换内循环:

for(ki = 0; ki < Cdim; ++ki) 
    for(kj = 0; kj < 8; ++kj)
        temp += (conv[ki][kj]) * (input[i - (Cdim1/2) + ki][j - (Cdim2/2) + kj]);
Run Code Online (Sandbox Code Playgroud)

编译:gcc so.c -Wall -Wextra -Ofast -mtune=native给了我矢量优化!

坏事:

  • 不要使用 8。尝试找到所需的最小填充并制作宏,以便它适用于维度 >= 8 的卷积矩阵
  • 用一些零填充输入,以便最后未定义的行为消失
  • 请注意,这实际上并没有提高您的性能。事实上它的工作速度更慢!
  • 请注意,如果您进一步修改它,按照以下顺序执行循环 for(ki) for(i) for(j) for(kj),则可以压缩几个周期。这可能是由于寄存器压力较小,因为每行转换可以存储更长时间。这也可能是我的 CPU 出现故障。
  • 您可能还想__attribute__ ((aligned (8)))在声明变量时考虑使用。在这种情况下,它没有改变任何东西,但在优化时你也想考虑这一点。当然,这仅适用于 GCC,并且您将需要针对 MSVC 的其他技巧。