在OpenMP中使用嵌套循环进行协方差计算

时间:2015-10-14 22:01:47

标签: fortran openmp

我有一段核心代码用于计算模型状态和观察之间的协方差,这些协方差将运行数十万次,我想使用OpenMP来加快速度。目前的实施似乎没有像我希望的那样加快速度。有一个嵌套循环,其中计算协方差。 N很大(约10 ^ 4),M相对较小(约10 ^ 2)。内循环计算两个和,并将一个和除以另一个。关于如何加快速度的任何建议?

  xa     = ...
  hxens  = ...
  istate = ...

  !$OMP PARALLEL DEFAULT(SHARED) PRIVATE(iupdt, iobs, mem_count, iens)                                            
  !$OMP DO
  do iupdt = 1,N                                        
     mem_count = 0

     do iens = 1,M
           cij(iupdt) = cij(iupdt) + xa(istate,iens) * hxens(iupdt,iens)   
           mem_count = mem_count + 1
     end do
     cij(iupdt) = cij(iupdt)/(mem_count-1)          
  end do
  !$OMP END DO          
  !$OMP END PARALLEL  

非常感谢任何帮助!

2 个答案:

答案 0 :(得分:1)

1)你可以删除mem_count:总和总是M。

2)你可以展开外循环:你有一个Haswell CPU,你将能够进行矢量化FMA。在32字节边界上对齐数组将使用编译器选项或指令提高性能。

3)如果你可以达到单精度,你可以获得额外的两倍因子。您可以以单精度累加并以双精度划分(如果xa * hxens总是相同的符号,请小心,你会非常不精确......)

4)你只能计算一次1.d0 /(dble(M)-1.d0)以避免做昂贵的div操作。

如果你有英特尔编译器,你在哪里分配数组:

  !DIR$ ATTRIBUTES ALIGN : 32 :: cij, hxens

然后,

  n8 = (n/8)*8                             
  f = 1.d0/(dble(M)-1.d0)

  !$OMP PARALLEL DEFAULT(SHARED) PRIVATE(iupdt, iobs, iens, l)       
  !$OMP DO
  do iupdt = 1,n8,8                                        

     do iens = 1,M 
       do l=0,7
         cij(iupdt+l) = cij(iupdt+l) + xa(istate,iens) * hxens(iupdt+l,iens)   
       end do
     end do

     do l=0,7
       cij(iupdt+l) = cij(iupdt+l) * f
     end do

  end do
  !$OMP END DO

  !$OMP END PARALLEL

  do iupdt = n8+1,n                                        
     do iens = 1,M
        cij(iupdt) = cij(iupdt) + xa(istate,iens) * hxens(iupdt,iens)   
     end do
     cij(iupdt) = cij(iupdt)*f          
  end do

答案 1 :(得分:0)

代码中的主要问题是数组xahxens的访问顺序。但是,如果交换iupdtiens循环,代码会表现得更好。如果cij的最终除法从iupdt循环中删除,并设置在它自己的新循环中,这很容易做到。此外,必须注意mem_count的积累是无用的,因为结果将永远是M

为了能够编译代码,我将片段放在子例程中。这就是它最终的样子:

subroutine covariance( xa, hxens, cij, istate, N, M )
    implicit none
    integer, intent(in)             :: istate, N, M 
    double precision, intent(in)    :: xa(N,M), hxens(N,M)
    double precision, intent(inout) :: cij(N)

    integer          :: iupdt, iens
    double precision :: ratio, xaiens

    ratio = 1.d0 / (M-1)
    !$omp parallel private( xaiens, iens, iupdt )
    !$omp do
    do iens = 1,M
        xaiens = xa(istate,iens)
        !$omp simd
        do iupdt = 1,N
             cij(iupdt) = cij(iupdt) + xaiens * hxens(iupdt,iens)   
        end do
        !$omp end simd
    end do
    !$omp end do
    !$omp do simd
    do iupdt = 1,N
        cij(iupdt) = cij(iupdt) * ratio          
    end do
    !$omp end do simd
    !$omp end parallel
end subroutine covariance

一些评论:

  • 所有内存访问现在都是线性的,这很好
  • 但是,cij以前可能只访问过一次(因为在iens循环期间缓存),现在访问iens次。这可能非常糟糕。但是,由于您说N大约为10000,这意味着cij大约为80KB,这将适合缓存。所以这不应该有任何后果。
  • 最里面的循环现在可以轻松地进行矢量化。此外,它非常适合使用融合乘法和加法运算,允许充分利用处理器的潜力。我已经添加了一个omp simd指令来强化它,但如果你的编译器不支持它,你可以删除它。无论如何,矢量化案例非常明显,我怀疑编译器是否需要它。同样是最后一个循环。
  • 计算如此有限,实际数据适合缓存,我不确定OpenMP并行化是如此有效/必要。我鼓励你尝试使用和不使用代码,并删除全部或部分代码,如果它证明反效果。我把所有可以合理设置的内容放在这里,但不要犹豫要修剪需要的东西。