繁体   English   中英

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

[英]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 matrixvector会发生什么?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM