为什么并行版本较慢?

乔马科

我想在矩阵上应用特定的过滤器。(从[0] [0]到结束)

A [i] [j] = 0.2 *(A [i] [j] + A [i + 1] [j] + A [i-1] [j] + A [i] [j + 1] + A [i] [j-1])

如果[i],[j]例如是[0] [0](矩阵中的第一个值),则我在左侧和上侧使用零作为值。

我试图理解为什么我的代码的并行版本比顺序的要慢。

当我使用多个线程进行计算时,我使用的事实是沿对角线有独立的工作。我故意将矩阵扩展两行和两个cols(用零填充),以简化过滤器的计算。

我还尝试了各种尺寸的矩阵(最大7000x7000)。

我的问题:http : //15418.courses.cs.cmu.edu/fall2017/lecture/progbasics/slide_032

顺序版本:

for (int i = 1; i < r-1; i++) {
    for (int j = 1; j < c-1; j++) {
        arr[i][j] = 0.2f * (arr[i][j] + arr[i][j - 1] + arr[i - 1][j] + arr[i][j + 1] + arr[i + 1][j]); 
        }
    }

并行版本:

int n = r - 2;  
for (int slice = 0; slice < 2 * n - 1; ++slice) {     //along the diagonals
        int z = (slice < n) ? 0 : slice - n + 1;
        #pragma omp parallel for schedule(static) //spawns threads 
        for (int j = z; j <= slice - z; ++j) {
            pl_arr[j + 1][slice - j + 1] = 0.2f * (pl_arr[j + 1][slice - j + 1] + pl_arr[j + 1][slice - j] + pl_arr[j][slice - j + 1] + pl_arr[j + 1][slice - j + 1 + 1] + pl_arr[j + 1 + 1][slice - j + 1]);
        }
}

其余代码:

int r = 7000, c = 7000;

r = r + 2;
c = c + 2;

/* initialize random seed: */
srand(time(NULL));

float **arr = (float **)malloc(r * sizeof(float *));
for (int i = 0; i < r; i++)
    arr[i] = (float *)malloc(c * sizeof(float));

float **pl_arr = (float **)malloc(r * sizeof(float *));
for (int i = 0; i < r; i++)
    pl_arr[i] = (float *)malloc(c * sizeof(float));


for (int i = 0; i < r; i++) {
    for (int j = 0; j < c; j++) {
        if ((i == 0) || (i == (r - 1)) || (j == 0) || (j == (c - 1)) ){
            arr[i][j] = 0;
            pl_arr[i][j] = 0;
        }
        else {
            arr[i][j] = rand() % 99  + 1;
            pl_arr[i][j] = arr[i][j];
        }
    }
}

#pragma omp parallel for schedule(static)-for构造拆分for循环,以便当前团队中的每个线程处理循环的不同部分。

结果:Paralle版本始终比顺序版本慢

山姆·瓦尔沙夫奇克

如果弄清楚循环的顺序版本中发生了什么,您会发现内部循环访问顺序的内存地址(或更准确地说,是三个内存范围,每个范围的地址都被顺序访问)。

现代的CPU很好,正在不断地访问连续的内存地址。这就是为什么在许多用例中std::vector可以比直观地提高速度的std::list原因。

现在,对循环的并行版本执行相同的操作。用铅笔在纸上弄清楚每根线最终打到什么。看起来它是垂直遍历矩阵,跨多个单独分配的行。不会是连续的内存地址,它们会无处不在;这不是最佳选择。

您可以简单地做到这一点,只需让每个线程捕获其正在破坏的原始内存地址,然后查看所有执行线程的组合捕获日志即可。现在将其与顺序版本的相同。

要增加伤害的余地:在典型的现代体系结构上,内存区域被划分为称为“缓存行”的较大块。看起来并行版本将有多个执行线程访问相邻的内存地址,并且其中许多将属于同一缓存行;当多个CPU执行单元必须写入同一高速缓存行时,即使写入每个高速缓存行中的不同地址,它们也必须执行复杂的歌舞程序,以避免踩到彼此的脚趾。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章