我使用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;
}
我知道关键部分会有所帮助。但是临界区很慢。
那么,我该如何解决这个问题?
答案 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
读取的元素只能进行一次计算。
无论如何,您当然可以交换i
和k
循环并保存减少量,但是a
上的内存访问模式将效率低下(错乱)-因此,您应该{ {3}}避免这种情况的循环。
现在,如果您查看block,它将自动生成SIMD代码。当然,如果需要,您可以应用自己的SIMD内部函数。但是,如果m
不能被4整除(您未使用原始版本),请确保正确处理边缘情况。
最后,如果您确实想要性能,请使用BLAS库中的函数(例如MKL)。如果您想尝试优化,那么有很多机会可以进行深入的研究。