[英]Optimising and why openmp is much slower than sequential way?
我是OpenMp编程的新手。 我写了一个简单的c程序来将矩阵与向量相乘。 不幸的是,通过比较执行时间,我发现OpenMP比Sequential方式慢得多。
这是我的代码(这里的矩阵是N * N int,vector是N int,结果是N long long):
#pragma omp parallel for private(i,j) shared(matrix,vector,result,m_size)
for(i=0;i<m_size;i++)
{
for(j=0;j<m_size;j++)
{
result[i]+=matrix[i][j]*vector[j];
}
}
这是顺序方式的代码:
for (i=0;i<m_size;i++)
for(j=0;j<m_size;j++)
result[i] += matrix[i][j] * vector[j];
当我使用999x999矩阵和999向量尝试这两个实现时,执行时间为:
顺序:5439 ms并行:11120 ms
我真的不明白为什么OpenMP比顺序算法慢得多(慢2倍!)任何人都可以解决我的问题?
您的代码部分遭受所谓的错误共享 ,这是所有缓存一致系统的典型代表。 简而言之, result[]
数组的许多元素都适合同一个缓存行。 当线程i
作为+=
运算符的结果写入result[i]
,保存result[]
部分的高速缓存行变脏。 然后,高速缓存一致性协议使其他核心中的该高速缓存行的所有副本无效,并且它们必须从高级高速缓存或主存储器刷新其副本。 result
是一个long long
的数组,然后一个高速缓存行(x86上的64个字节)保存8个元素,除了result[i]
,在同一个高速缓存行中还有7个其他数组元素。 因此,两个“相邻”线程可能会不断争夺高速缓存行的所有权(假设每个线程在一个单独的核心上运行)。
为了减轻您的情况下的错误共享,最简单的方法是确保每个线程都获得一个迭代块,其大小可以被缓存行中的元素数量整除。 例如,您可以应用schedule(static,something*8)
,其中something
应该足够大,以便迭代空间不会碎片化为太多碎片,但同时它应该足够小,以便每个线程获得一个块。 例如,对于m_size
等于999和4个线程,您可以将schedule(static,256)
子句应用于parallel for
construct。
代码运行速度较慢的另一个部分原因可能是,当启用OpenMP时,编译器可能不愿意在分配共享变量时应用某些代码优化。 OpenMP提供了所谓的宽松内存模型,允许每个线程中共享变量的本地内存视图不同,并提供flush
构造以同步视图。 但是编译器通常会将共享变量视为隐式volatile
如果它们无法证明其他线程不需要访问去同步的共享变量。 你的情况就是其中之一,因为result[i]
只被分配给,而result[i]
值从未被其他线程使用过。 在串行情况下,编译器很可能会创建一个临时变量来保存内部循环的结果,并且只有在内部循环结束后才会分配给result[i]
。 在并行的情况下,它可能会决定这将在其他线程中创建result[i]
的临时去同步视图,因此决定不应用优化。 仅仅为了记录,带有-O3 -ftree-vectorize
GCC 4.7.1在启用OpenMP和不启用OpenMP时执行临时变量技巧。
因为当OpenMP在线程之间分配工作时,会进行大量的管理/同步,以确保共享矩阵和向量中的值不会以某种方式损坏。 尽管它们是只读的:人类很容易看到,但编译器可能没有。
出于教学原因尝试的事情:
0)如果不shared
matrix
和vector
会发生什么?
1)首先并行化内部“j-loop”,保持外部“i-loop”串行。 走着瞧吧。
2)不要在result[i]
收集和,而是在变量temp
,只有在内部循环结束后才将其内容分配给result[i]
以避免重复的索引查找。 在内循环开始之前,不要忘记将temp
为0。
我是在参考Hristo的评论时这样做的。 我尝试使用schedule(static,256)。 对我而言,它无助于更改默认的chunck大小。 也许它甚至会使情况变得更糟。 我打印出线程号及其索引,无论是否设置了时间表,很明显OpenMP已经选择了线程索引彼此远离,因此错误共享似乎不是问题。 对我来说,这个代码已经为OpenMP提供了很好的推动力。
#include "stdio.h"
#include <omp.h>
void loop_parallel(const int *matrix, const int ld, const int*vector, long long* result, const int m_size) {
#pragma omp parallel for schedule(static, 250)
//#pragma omp parallel for
for (int i=0;i<m_size;i++) {
//printf("%d %d\n", omp_get_thread_num(), i);
long long sum = 0;
for(int j=0;j<m_size;j++) {
sum += matrix[i*ld +j] * vector[j];
}
result[i] = sum;
}
}
void loop(const int *matrix, const int ld, const int*vector, long long* result, const int m_size) {
for (int i=0;i<m_size;i++) {
long long sum = 0;
for(int j=0;j<m_size;j++) {
sum += matrix[i*ld +j] * vector[j];
}
result[i] = sum;
}
}
int main() {
const int m_size = 1000;
int *matrix = new int[m_size*m_size];
int *vector = new int[m_size];
long long*result = new long long[m_size];
double dtime;
dtime = omp_get_wtime();
loop(matrix, m_size, vector, result, m_size);
dtime = omp_get_wtime() - dtime;
printf("time %f\n", dtime);
dtime = omp_get_wtime();
loop_parallel(matrix, m_size, vector, result, m_size);
dtime = omp_get_wtime() - dtime;
printf("time %f\n", dtime);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.