1

我刚刚开始使用我的矢量化代码。我的矩阵向量乘法代码没有被自动向量化gcc,我想知道为什么。此 pastebin 包含来自 -fopt-info-vec-missed.

我无法理解输出告诉我的内容以及它如何与我用代码编写的内容相匹配。

例如,我看到很多行说not enough data-refs in basic block,我无法通过谷歌搜索在网上找到很多关于此的详细信息。我还看到与内存对齐有关的问题,例如Unknown misalignment, naturally alignedvector alignment may not be reachable. 我所有的内存分配都是针对double使用的类型malloc,我相信它可以保证与该类型对齐。

环境:gcc在 WSL2 上编译

gcc -v: gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

#define N 4000 // Matrix size will be N x N
#define T 1

//gcc -fopenmp -g vectorisation.c -o main -O3 -march=native -fopt-info-vec-missed=missed.txt

void doParallelComputation(double *A, double *V, double *results, unsigned long matrixSize, int numThreads)
{
    omp_set_num_threads(numThreads);
    unsigned long i, j;

    #pragma omp parallel for simd private(j)
    for (i = 0; i < matrixSize; i++)
    {
        // double *AHead = &A[i * matrixSize];
        // double tmp = 0;

        for (j = 0; j < matrixSize; j++)
        {
            results[i] += A[i * matrixSize + j] * V[j];
            // also tried tmp += A[i * matrixSize + j] * V[j];
        }
        // results[i] = tmp;
    }

}

void genRandVector(double *S, unsigned long size)
{
    srand(time(0));
    unsigned long i;

    for (i = 0; i < size; i++)
    {
        double n = rand() % 5;
        S[i] = n;
    }
}

void genRandMatrix(double *A, unsigned long size)
{
    srand(time(0));
    unsigned long i, j;
        for (i = 0; i < size; i++)
        {
            for (j = 0; j < size; j++)
            {
                double n = rand() % 5;
                A[i*size + j] = n;
            }

        }
    }

int main(int argc, char *argv[])
{

    double *V = (double *)malloc(N * sizeof(double));     // v in our A*v = parV computation
    double *parV = (double *)malloc(N * sizeof(double));  // Parallel computed vector
    double *A = (double *)malloc(N * N * sizeof(double)); // NxN Matrix to multiply by V
    genRandVector(V, N);
    doParallelComputation(A, V, parV, N, T);

    free(parV);
    free(A);
    free(V);
    
    return 0;
}
4

1 回答 1

3

double *restrict results没有OpenMP但使用-ffast-math. https://godbolt.org/z/qaPh1v

您需要专门告诉 OpenMP 有关减少的信息,以使其放松 FP-math 关联性。(-ffast-math对 OpenMP 矢量化器没有帮助)。同样,我们得到您想要的:
#pragma omp simd reduction(+:tmp)


使用 justrestrict和 no -ffast-mathor -fopenmp,您将得到全部垃圾:它执行 SIMD FP 乘法,但随后将其解压缩为 4 倍vaddsd到标量累加器中,根本无助于隐藏 FP 延迟。

restrict-fopenmp(没有快速数学),它只是做标量 FMA。

使用restrictand -ffast-math不带-fopenmp#pragma注释)它可以很好地自动矢量化:vfmadd231pd ymm在循环内部,随机播放/在外部添加水平总和。(但不并行化)。 https://godbolt.org/z/f36oG3

使用restrictand -ffast-math( with -fopenmp ) 它仍然不会自动矢量化。OpenMP 向量化器是不同的,可能没有利用快速数学,而是需要你告诉它减少?


另请注意,对于您的数据布局,您要并行化的循环(外部)与您要使用 SIMD 向量化的循环(内部)不同。 内部点积循环的两个输入“向量”都在连续内存中,因此读取它们最有意义,而不是尝试将来自 4 个不同列的 SIMD 数据混洗到一个向量中以累积 4 个result[i+0..3]结果为 1 个向量。

但是,将外循环展开 4 以将每个循环V[j+0..3]与来自 4 个不同列的数据一起使用将提高计算强度(每个 FMA 接近 1 个负载,而不是 2 个)

(只要矩阵V[] 一行适合 L1d 缓存,这很好。如果不是,它实际上很糟糕,应该被缓存阻塞。或者实际上,如果你展开外循环,矩阵的 4 行。)


另请注意,这double tmp = 0;将是一个好主意:您当前的版本添加到result[i],在写入之前阅读它。在您将其用作纯输出之前,这将需要零初始化。

自动 vec 自动标准版本:

我认为这是正确的;asm 看起来像自动并行化以及自动矢量化内部循环。

void doParallelComputation(double *restrict A, double *restrict V, double *restrict results, unsigned long matrixSize, int numThreads)
{
    omp_set_num_threads(numThreads);
    unsigned long i, j;

    #pragma omp parallel for private(j)
    for (i = 0; i < matrixSize; i++)
    {
        // double *AHead = &A[i * matrixSize];
        double tmp = 0;

         // TODO: unroll outer loop and cache-block it.
        #pragma omp simd reduction(+:tmp)
        for (j = 0; j < matrixSize; j++)
        {
            //results[i] += A[i * matrixSize + j] * V[j];
            tmp += A[i * matrixSize + j] * V[j];  // 
        }
        results[i] = tmp;  // write-only to results, not adding to old value.
    }

}

使用 OpenMPified 辅助函数内的矢量化内循环编译 ( Godbolt )doParallelComputation._omp_fn.0:

# gcc7.5 -xc -O3 -fopenmp -march=skylake
.L6:
        add     rdx, 1               # loop counter; newer GCC just compares the end-pointer
        vmovupd ymm2, YMMWORD PTR [rcx+rax]            # 32-byte load
        vfmadd231pd     ymm0, ymm2, YMMWORD PTR [rsi+rax]  # 32-byte memory-source FMA
        add     rax, 32                                # pointer increment
        cmp     rdi, rdx
        ja      .L6

然后是循环后效率平庸的水平总和;不幸的是,OpenMP 矢量化器不如“普通”-ftree-vectorize矢量化器聪明,但这需要-ffast-math在这里做任何事情。

于 2020-10-11T05:35:50.497 回答