如何完美答案同时使用openmp和AVX2?

时间:2018-07-01 03:30:21

标签: c multithreading openmp avx2

我使用OpenMP和AVX2编写了Matrix-Vector产品程序。

但是,由于OpenMP,我得到了错误的答案。 真正的答案是数组c的所有值都将变为100。

我的答案是98、99和100的组合。

实际代码如下。

我用-fopenmp,-mavx,-mfma编译了Clang。

#include "stdio.h"
#include "math.h"
#include "stdlib.h"
#include "omp.h"
#include "x86intrin.h"

void mv(double *a,double *b,double *c, int m, int n, int l)
{
    int k;
#pragma omp parallel
    {
        __m256d va,vb,vc;
        int i;
#pragma omp for private(i, va, vb, vc) schedule(static)
        for (k = 0; k < l; k++) {
            vb = _mm256_broadcast_sd(&b[k]);
            for (i = 0; i < m; i+=4) {
                va = _mm256_loadu_pd(&a[m*k+i]);
                vc = _mm256_loadu_pd(&c[i]);

                vc = _mm256_fmadd_pd(vc, va, vb);

                _mm256_storeu_pd( &c[i], vc );
            }
        }
    }
}
int main(int argc, char* argv[]) {

    // set variables
    int m;
    double* a;
    double* b;
    double* c;
    int i;

    m=100;
    // main program

    // set vector or matrix
    a=(double *)malloc(sizeof(double) * m*m);
    b=(double *)malloc(sizeof(double) * m*1);
    c=(double *)malloc(sizeof(double) * m*1);
    //preset
    for (i=0;i<m;i++) {
        a[i]=1;
        b[i]=1;
        c[i]=0.0;
    }
    for (i=m;i<m*m;i++) {
        a[i]=1;
    }

    mv(a, b, c, m, 1, m);

    for (i=0;i<m;i++) {
        printf("%e\n", c[i]);
    }
    free(a);
    free(b);
    free(c);
    return 0;
}

我知道关键部分会有所帮助。但是临界区很慢。

那么,我该如何解决这个问题?

2 个答案:

答案 0 :(得分:3)

您想要的基本操作是

Thread

如果您使用row-major order storage,则会变成

c[i] = a[i,k]*b[k]

如果您使用列主要订单存储,它将变为

c[i] = a[i*l + k]*b[k]

对于大行顺序,您可以像这样并行化

c[i] = a[k*m + i]*b[k]

对于列大订单,您可以像这样并行化

#pragma omp parallel for
for(int i=0; i<m; i++) {
  for(int k=0; k<l; k++) {
    c[i] += a[i*l+k]*b[k];
  }
}

矩阵向量操作是2级操作,它们是内存带宽绑定操作。 1级和2级操作无法根据内核数量进行扩展。只能缩放https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3的3级操作(例如,密集矩阵乘法)。

答案 1 :(得分:2)

问题与您的AVX内部函数无关,让我们看一下没有内部函数的代码:

void mv(double *a,double *b,double *c, int m, int n, int l)
{
    #pragma omp parallel for schedule(static)
    for (int k = 0; k < l; k++) {
        double xb = b[k];
        for (int i = 0; i < m; i++) {
            double xa = a[m*k+i];
            double xc = c[i];
            xc = xc + xa * xb;
            c[i] = xc;
        }
    }
}

注意:您的私有声明在技术上是正确的,并且是多余的,因为在并行循环内部进行了声明,但是如果您尽可能在本地声明变量,则对代码进行推理要容易得多。

代码上的竞争条件位于c[i]上-多个线程尝试更新。现在,即使您可以通过原子更新来保护它,性能也将是可怕的:不仅因为受到保护,而且因为c[i]的数据必须在不同内核的缓存之间不断地移动。

您可以做的一件事是在c上使用数组归约。这将为每个线程制作c的私有副本,并在最后合并它们:

void mv(double *a,double *b,double *c, int m, int n, int l)
{
    #pragma omp parallel for schedule(static) reduction(+:c[:m])
    for (int k = 0; k < l; k++) {
        for (int i = 0; i < m; i++) {
            c[i] += a[m*k+i] * b[k];
        }
    }
}

只要两个m-向量适合您的缓存,这应该是相当有效的,但是由于线程管理的开销,您仍然可能会得到很多开销。最终,您将受到内存带宽的限制,因为在矢量矩阵乘法中,每个从a读取的元素只能进行一次计算。

无论如何,您当然可以交换ik循环并保存减少量,但是a上的内存访问模式将效率低下(错乱)-因此,您应该{ {3}}避免这种情况的循环。

现在,如果您查看block,它将自动生成SIMD代码。当然,如果需要,您可以应用自己的SIMD内部函数。但是,如果m不能被4整除(您未使用原始版本),请确保正确处理边缘情况。

最后,如果您确实想要性能,请使用BLAS库中的函数(例如MKL)。如果您想尝试优化,那么有很多机会可以进行深入的研究。