优化和为什么openmp比顺序方式慢得多?

时间:2013-05-04 07:03:24

标签: c performance vector matrix openmp

我是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倍!)任何可以解决我问题的人?

3 个答案:

答案 0 :(得分:13)

您的代码部分受到所谓的false sharing的影响,这是所有缓存一致系统的典型代码。简而言之,result[]数组的许多元素都适合同一个缓存行。作为i运算符的结果,当线程result[i]写入+=时,保存result[]部分的缓存行变脏。然后,高速缓存一致性协议使其他核心中的该高速缓存行的所有副本无效,并且它们必须从高级高速缓存或主存储器刷新其副本。由于resultlong 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)如果matrixvector不是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);

}