我有一段核心代码用于计算模型状态和观察之间的协方差,这些协方差将运行数十万次,我想使用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
非常感谢任何帮助!
答案 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)
代码中的主要问题是数组xa
和hxens
的访问顺序。但是,如果交换iupdt
和iens
循环,代码会表现得更好。如果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
指令来强化它,但如果你的编译器不支持它,你可以删除它。无论如何,矢量化案例非常明显,我怀疑编译器是否需要它。同样是最后一个循环。