我是OpenMp编程的新手。我写了一个简单的c程序来将矩阵与向量相乘。不幸的是,通过比较执行时间,我发现OpenMP比顺序方式慢得多。
这是我的代码(这里的矩阵是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毫秒 平行:11120毫秒
我真的不明白为什么OpenMP比顺序算法慢得多(慢2倍!)任何可以解决我问题的人?
答案 0 :(得分:13)
您的代码部分受到所谓的false sharing的影响,这是所有缓存一致系统的典型代码。简而言之,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
构造。
代码运行速度较慢的另一个部分原因可能是,当启用OpenMP时,编译器可能不愿意在分配共享变量时应用某些代码优化。 OpenMP提供了所谓的宽松内存模型,允许每个线程中共享变量的本地内存视图不同,并提供flush
结构以同步视图。但是,如果共享变量无法证明其他线程不需要访问去同步的共享变量,那么通常会将共享变量视为隐式volatile
。您的情况就是其中之一,因为result[i]
仅被分配给result[i]
,而result[i]
的值从未被其他线程使用。在串行情况下,编译器很可能会创建一个临时变量来保存内部循环的结果,并且只有在内部循环结束后才会分配给result[i]
。在并行的情况下,它可能会决定这将在其他线程中创建-O3 -ftree-vectorize
的临时去同步视图,因此决定不应用优化。仅仅为了记录,带有{{1}}的GCC 4.7.1在启用OpenMP和不启用OpenMP的情况下执行临时变量技巧。
答案 1 :(得分:2)
因为当OpenMP在线程之间分配工作时,会进行大量的管理/同步,以确保共享矩阵和向量中的值不会以某种方式损坏。尽管它们是只读的:人类很容易看到,但编译器可能没有。
出于教学原因尝试的事情:
0)如果matrix
和vector
不是shared
会怎样?
1)首先并行化内部“j-loop”,保持外部“i-loop”串行。看看会发生什么。
2)不要在result[i]
中收集总和,而是在变量temp
中收集总和,并在内循环完成后将其内容分配给result[i]
,以避免重复的索引查找。在内循环开始之前,不要忘记将temp
初始化为0.
答案 2 :(得分: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);
}