【问题标题】:With OpenMP parallelized nested loops run slow使用 OpenMP 并行化嵌套循环运行缓慢
【发布时间】:2015-04-01 07:59:01
【问题描述】:

我有一个 fortran 程序的一部分,其中包含一些我想与 OpenMP 并行化的嵌套循环。

integer :: nstates , N, i, dima, dimb, dimc, a_row, b_row, b_col, c_row, row, col
double complex, dimension(4,4):: mat
double complex, dimension(:), allocatable :: vecin,vecout 

nstates = 2
N = 24

allocate(vecin(nstates**N), vecout(nstates**N))
vecin = ...some data
vecout = 0

mat = reshape([...some data...],[4,4])

dimb=nstates**2

!$OMP PARALLEL DO PRIVATE(dima,dimc,row,col,a_row,b_row,c_row,b_col) 
do i=1,N-1
    dima=nstates**(i-1)
    dimc=nstates**(N-i-1)

    do a_row = 1, dima
        do b_row = 1,dimb
            do c_row = 1,dimc
                row = ((a_row-1)*dimb + b_row - 1)*dimc + c_row
                do b_col = 1,dimb
                    col = ((a_row-1)*dimb + b_col - 1)*dimc + c_row
                    !$OMP ATOMIC
                    vecout(row) = vecout(row) + vecin(col)*mat(b_row,b_col)
                end do
            end do
        end do
    end do
end do
!$OMP END PARALLEL DO 

程序运行,我得到的结果也是正确的,只是慢得令人难以置信。比没有 OpenMP 慢得多。我对 OpenMP 了解不多。我在使用 PRIVATE 或 OMP ATOMIC 时做错了吗?对于如何提高我的代码性能的每一个建议,我将不胜感激。

【问题讨论】:

  • 您应该看一下vecoutreduction 子句。这应该加快速度;-)
  • 在最内层循环中使用原子指令不是一个好主意!
  • 感谢您的回答。为什么原子指令是一个坏主意?我删除了原子指令并改用了 REDUCTION(+:vecout) ,这导致了分段错误。归约和数组有​​什么特别之处吗?
  • 缩减工作仅适用于小型数组而不会出现分段错误。
  • 那你需要增加栈大小:ulimit -s unlimited

标签: parallel-processing fortran openmp


【解决方案1】:

如果您的数组太大并且自动归约导致堆栈溢出,您可以使用可分配的临时数组自己实现归约。

正如 Francois Jacq 指出的那样,您还有一个由 dimadimb 引起的竞争条件,它们应该是私有的。

double complex, dimension(:), allocatable :: tmp

!$OMP PARALLEL PRIVATE(dima,dimb,row,col,a_row,b_row,c_row,b_col,tmp)

allocate(tmp(size(vecout)))
tmp = 0

!$OMP DO
do i=1,N-1
    dima=nstates**(i-1)
    dimc=nstates**(N-i-1)

    do a_row = 1, dima
        do b_row = 1,dimb
            do c_row = 1,dimc
                row = ((a_row-1)*dimb + b_row - 1)*dimc + c_row
                do b_col = 1,dimb
                    col = ((a_row-1)*dimb + b_col - 1)*dimc + c_row
                    tmp(row) = tmp(row) + vecin(col)*mat(b_row,b_col)
                end do
            end do
        end do
    end do
end do
!$OMP END DO

!$OMP CRITICAL
vecout = vecout + tmp
!$OMP END CRITICAL
!$OMP END PARALLEL

【讨论】:

  • 谢谢,成功了。但是为什么这种方法比使用 atomic 更快呢?
  • 变量 dima 和 dimc 应该是私有的
  • 是的,我以前认识到这一点,现在在我的问题中进行了更改。
  • 在我看来,它们仍然是竞争条件,因为两个线程可能具有相同的行索引。请参阅我提出的解决方案。
  • @FrancoisJacq 它们可以具有相同的行索引,但它们写入不同的数组。您的解决方案很好,但是每个!$omp parallel do 都有很多同步。最终决定应来自绩效衡量。
【解决方案2】:

你可以试试这样的:

do b_col=1,dimb
   do i=1,N-1
      dima=nstates**(i-1)
      dimc=nstates**(N-i-1)
      !$OMP PARALLEL DO COLLAPSE(3) PRIVATE(row,col,a_row,b_row,c_row)
      do a_row = 1, dima
         do b_row = 1,dimb
            do c_row = 1,dimc
                row = ((a_row-1)*dimb + b_row - 1)*dimc + c_row
                col = ((a_row-1)*dimb + b_col - 1)*dimc + c_row
                vecout(row) = vecout(row) + vecin(col)*mat(b_row,b_col)
            enddo
         enddo
      enddo
   enddo
enddo

优点是 // 循环现在不会导致冲突:所有索引行都不同。

【讨论】:

  • 感谢您的回答。我将测量您和 Vladimir F 解决方案的时间以比较它们。到目前为止,我可以说的是,您的解决方案在没有 COLLAPSE 的情况下对我来说运行得更快。你能告诉我为什么你把 b_col 循环放在其他循环之外吗?当 b_col 循环在内部时,每个线程的行索引也应该不同,不是吗?
  • 循环 b_col 在循环二之外 // 避免线程在修改 vecout 时计算相同的行索引 => 竞争情况(随机错误的风险)。为什么要删除 COLLAPSE 子句?它只是表明接下来的三个循环可以合并为一个 // 一个。
  • row = ((a_row-1)*dimb + b_row - 1)*dimc + c_row ,所以 row 不依赖于 b_col。对于您的崩溃问题:首先我尝试了崩溃,然后没有。没有它会更快,我为什么要使用它?
  • 因为row不依赖于b_col,所以相同的row index可能会获得b_col次。关于 COLLAPSE,如果你有测量和没有测量,那么一切都很好:评判始终是测量!
  • 在我之前的消息中,阅读“dimb times”而不是“b_col times”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-14
  • 2013-12-08
相关资源
最近更新 更多