我正在对不适合缓存的数组进行大量计算(这里的衍生物,但看起来类似于图像操作),这意味着CPU必须在缓存中加载部件,计算,然后加载另一部件等等。但是因为对于计算的形状,一些数据是多次加载,卸载和重新加载。我想知道是否有办法优化这一点。我已经使用编译器优化(GCC和Intel)使用SIMD指令。
这是Fortran计算,但它类似于C / C ++,内存顺序只是反转而数组使用()
而不是[]
。 for
替换为do
。
在x ax上:
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
DF(i,j,k)=(F(i+1,j,k)-F(i-1,j,k))*B+(F(i-2,j,k)-F(i+2,j,k))*C
end do
end do
end do
on y ax:
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
DF(i,j,k)=(F(i,j+1,k)-F(i,j-1,k))*B+(F(i,j-2,k)-F(i,j+2,k))*C
end do
end do
end do
on z ax:
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
DF(i,j,k)=(F(i,j,k+1)-F(i,j,k-1))*B+(F(i,j,k-2)-F(i,j,k+2))*C
end do
end do
end do
ax x上的一阶导数是正常的,因为连续读取内存。 y轴和z轴上的导数不连续。
最糟糕的计算是我将所有轴组合在一起(这是拉普拉斯算子):
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
V(i,j,k) = M(i,j,k,1) * p(i,j,k) &
& + M(i,j,k,2) * p(i-1,j,k) &
& + M(i,j,k,3) * p(i+1,j,k) &
& + M(i,j,k,4) * p(i,j-1,k) &
& + M(i,j,k,5) * p(i,j+1,k) &
& + M(i,j,k,6) * p(i,j,k-1) &
& + M(i,j,k,7) * p(i,j,k+1)
end do
end do
end do
请注意,编译器不理解上一次操作(拉普拉斯算子)。要使用SIMD(矢量化计算),我需要像这样拆分操作,这样可以加速2.5倍:
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
V(i,j,k) = M(i,j,k,1) * p(i,j,k) &
& + M(i,j,k,2) * p(i-1,j,k) &
& + M(i,j,k,3) * p(i+1,j,k)
end do
end do
end do
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
V(i,j,k) = V(i,j,k) + &
& + M(i,j,k,4) * p(i,j-1,k) &
& + M(i,j,k,5) * p(i,j+1,k)
end do
end do
end do
do k=1,N(3)
do j=1,N(2)
do i=3,N(1)
V(i,j,k) = V(i,j,k) + &
& + M(i,j,k,6) * p(i,j,k-1) &
& + M(i,j,k,7) * p(i,j,k+1)
end do
end do
end do
也许使用SIMD我已达到最高速度,但由于这些计算需要数天,即使使用MPI和超过1024 CPU,减少计算时间,即使是20%也是一个很好的步骤! 你们中有谁有关于如何优化这个的想法吗?
答案 0 :(得分:3)
当您使用3D模具并引用i,j,k-1
,i,j,k+1
等元素时,您通过数组的线性顺序将不是最佳的。缓存效率可以提高loop tiling。
在我的代码中我使用
!$omp parallel private(i,j,k,bi,bj,bk)
!$omp do schedule(runtime) collapse(3)
do bk = 1, Unz, tnz
do bj = 1, Uny, tny
do bi = 1, Unx, tnx
do k = bk, min(bk+tnz-1,Unz)
do j = bj, min(bj+tny-1,Uny)
do i = bi, min(bi+tnx-1,Unx)
U2 (i,j,k) = U2(i,j,k) + &
(U(i+1,j,k)-U(i,j,k)) * ...
U2(i,j,k) = U2(i,j,k) - &
(U(i,j,k)-U(i-1,j,k)) * ...
U2(i,j,k) = U2(i,j,k) + &
(U(i,j+1,k)-U(i,j,k)) * ...
U2(i,j,k) = U2(i,j,k) - &
(U(i,j,k)-U(i,j-1,k)) * ...
U2(i,j,k) = U2(i,j,k) + &
(U(i,j,k+1)-U(i,j,k)) * ...
U2(i,j,k) = U2(i,j,k) - &
(U(i,j,k)-U(i,j,k-1)) * ...
end do
end do
end do
end do
end do
end do
!$omp end do
其中tnx
,tny
,tnz
是您在i,j,k
顺序中迭代的磁贴的大小。必须将大小设置为接近L1高速缓存。这将增加重新加载加载到缓存中的内容。
如果您需要分开指示,您当然可以这样做并仍然保留平铺。