我正在构建一个应将两个矩阵相乘的程序。问题是,我应该使用OpenMP并行化来执行此操作,但是每次都只在一个“ for”循环上执行(首先是外循环,然后是其子循环,然后是内部循环),对于每种并行化方法,我应该在使用时分析结果不同数量的线程(1,2,4,8,16,32,64,128)。
我的问题是,我应该将OpenMP的并行/私有部分放在哪里,应该私有/共享哪些变量来完成此操作?
// code to be parallelized using n_threads
omp_set_dynamic(0); // Explicitly disable dynamic teams
omp_set_num_threads(n_threads);
#pragma omp parallel for shared(a, b, c) private(i,j)
for (i=0; i < TAM_MATRIZ; i++){
for (j=0; j < TAM_MATRIZ; j++) {
c[i][j] = 0; // initialize the result matrix with zeros
for (k=0; k < TAM_MATRIZ; k++){
#pragma omp atomic
c[i][j] += a[i][k]*b[k][j];
}
}
printf("Number of threads used: %d\n", omp_get_num_threads());
}
编辑
实际上,它是三个程序,第一个程序仅并行化外部循环,第二个程序仅并行化中间循环,最后一个并行化内部循环。 每个版本应使用指定的线程(1,2,4,8,16,32,64,128)运行8次,然后将性能与不同程序版本和使用不同线程号的相同版本进行比较。
我的疑问在于共享或私有化它的位置。在并行化第一个循环时,应共享哪些变量?当我在第二个循环中工作时,共享哪些变量?等等...
在我看来,我不能共享任何变量,因为我将有多个线程同时工作并且可以产生部分结果,但是我知道我错了,我在这里基本上是在问为什么。< / p>
答案 0 :(得分:2)
您步入正轨-这实际上非常简单。
您在k
子句中错过了private
-这会导致问题,因为在外部定义时默认为shared
。最好的方法是尽可能在本地声明变量(例如for (int ...)
,而不是显式选择每个变量的数据共享,这几乎总是需要的,并且更容易推理。a
,{ {1}},b
来自外部,并且是隐式的c
-循环变量是在内部且隐式声明的shared
。
幸运的是,不需要private
。每个线程在不同的#pragma omp atomic
上工作-因此,没有两个线程可以尝试更新相同的i
。删除c[i][j]
将大大提高性能。如果您需要原子,也可以考虑将还原作为替代方法。
如果要打印atomic
,则应在循环之外但在并行区域内进行打印。就您而言,这意味着您必须将omp_get_num_threads
分为omp parallel for
和omp parallel
。使用omp for
确保只有一个线程输出。
请注意,矩阵乘法带来的出色性能要复杂得多,超出了本问题的范围。
编辑:
对于嵌套循环,通常最好并行化最外面的循环-即,没有阻止它的数据依赖项。在某些情况下,最外面的循环无法产生足够的并行度-在这种情况下,您宁愿使用omp single
来并行化外面的2个循环。除非您完全知道自己在做什么,否则不要两次使用collapse(2)
。原因是并行化中间循环会产生更多的工作,从而增加了相对的开销。
在您的特定情况下,可以安全地假设(parallel) for
0 ,这意味着最外层的循环具有足够的并行工作量,可以有效地使用所有线程。
重申数据共享规则。对于正常的TAM_MATRIZ >> n_threads
区域。
parallel
区域的词法范围内定义的变量(和并行循环变量)是隐式私有的。这些是线程处理的变量。如果仅在词法范围内使用变量,则始终 1 在尽可能狭窄的词法范围内对其进行定义。如果遵循此规则,几乎不需要显式定义parallel
/ private
数据共享属性 2 。
0 否则,在这里使用OpenMP甚至毫无意义。
1 异常适用于具有昂贵ctor的非平凡C ++类型。
2 shared
/ reduction
对于明确使用很有用。
答案 1 :(得分:2)
您应该考虑像这样交换j
和k
循环的顺序
memset(c, 0, sizeof(c[0][0])*TAM_MATRIZ*TAM_MATRIZ);
#pragma omp parallel for
for (int i=0; i < TAM_MATRIZ; i++)
for (int k=0; k < TAM_MATRIZ; k++)
for (int j=0; j < TAM_MATRIZ; j++)
c[i][j] += a[i][k]*b[k][j];
与并行化相比,这可能会带来更多的性能优势,因为它可以更好地利用缓存。但是它还有一个更细微的好处:它消除了求和中的依赖链。
浮点数学不是关联的,这意味着(a + b)+ c不一定等于a +(b + c)。当您的内循环经过k
时,则每次迭代都将其写入相同的变量c[i][j]
。如果您更改顺序(例如使用SIMD)进行求和,则可能会得到不同的结果。没有OpenMP或较宽松的浮点模型,例如如果使用-Ofast
,则在对浮点有依赖性时,您的编译器(默认情况下允许关联浮点算术的ICC除外)可能甚至不会使用SIMD。
但是,当内部循环结束j
时,每次迭代c[i][j]
都会写入不同的元素,因此依赖关系将被破坏。打破依赖关系不仅可以为您带来一致的结果,而与线程数或SIMD宽度无关,还可以通过更好地使用缓存来提高性能,并且即使在使用严格的浮点模型的情况下也可以实现SIMD。