我必须解决双积分
在我的程序中,可以在以下最小工作示例中转换为i-,j-
循环:
program test
implicit none
integer :: i, j, n
double precision, allocatable :: y(:), res(:), C(:,:,:)
n=200
allocate(y(n), res(n), C(n,n,n))
call random_number(y)
call random_number(C)
res = 0.d0
do i=1, n
do j=1, n
res(:) = res(:) + y(i) * y(j) * C(:, j, i)
end do
end do
deallocate(y, res, C)
end program test
我必须每次执行多次解决这个积分,并且分析告诉我,它是我计算的瓶颈,耗费了超过95%的执行时间。
我想知道是否有可能以更聪明,即快速的方式解决这个问题,并且可能会摆脱一个或两个循环。
我的问题不是用编译器标志或并行化来优化代码,而是双循环是否是解决给定问题的最佳实践。通常循环很慢,我试图避免它们。我在想通过重塑或扩展数组来避免循环是可能的。但我只是没有看到它。
答案 0 :(得分:2)
如果以Matrix表示法编写双循环,y(i)*y(j)
将成为二元YY^t
,Y为n x 1矩阵。有了这个,你可以重新编写循环到(伪代码)
do n=1,size(C,1)
res(n) = sum( YY^t * C_n )
enddo
其中C_n = C(n,:,:)
和*
是逐元素乘法。除了你已经完成的元素计算之外,这还有两种计算结果的方法:
res(n) = sum( (YY^t) * C_n )
res(n) = sum( Y * (Y^t C_n) )
在这两种情况下,拥有连续数据并重新排序数组C
是有益的:
do i=1,n
C2(:,:,i) = C(i,:,:)
enddo !i
浮点运算的数量与两种方法相同,略低于原始方法。所以让我们衡量所有人的时间......
以下是使用LAPACK进行矩阵运算的实现(并在适用的情况下使用点积):
<强> 1。 sum( (YY^t) * C_n )
强>
call system_clock(iTime1)
call dgemm('N','N',n,n,1,1.d0,y,n,y,1,0.d0,mat,n)
nn=n*n
do i=1,n
res(i) = ddot( nn, mat, 1, C2(:,:,i), 1 )
enddo !i
<强> 2。 sum( Y * (Y^t C_n) )
强>
do i=1,n
call dgemm('N','N',1,n,n,1.d0,y,1,C2(:,:,i),n,0.d0,vec,1)
res(i) = ddot( n, y, 1, vec, 1 )
enddo !i
结果如下:
Orig: 0.111000001
sum((YY^t)C): 0.116999999
sum(Y(Y^tC)): 0.187000006
您原来的实施速度最快!为什么?最可能是由于CPU上缓存的理想使用。 Fortran编译器通常在优化循环方面非常聪明,并且在元素计算中,您只需添加和缩放向量,而无需任何矩阵运算。这可以非常有效地利用。
那么,还有改进的余地吗?当然:)你在循环中执行的操作通常称为axpy:y = a*x + y
。这是一个常用的BLAS子程序 - 通常是高度优化的。
利用这个导致
res = 0.d0
do i=1, n
do j=1, n
call daxpy(n, y(i)*y(j), C(:,j,i), 1, res, 1)
end do
end do
并采取
Orig (DAXPY): 0.101000004
大约快10%。
以下是完整的代码,所有测量都是使用OpenBLAS和n=500
进行的(以便更好地了解影响)
program test
implicit none
integer :: i, j, n, nn
double precision, allocatable, target :: y(:), res(:), resC(:), C(:,:,:), C2(:,:,:), mat(:,:), vec(:)
integer :: count_rate, iTime1, iTime2
double precision :: ddot
n=500
allocate(y(n), res(n), resC(n), C(n,n,n), C2(n,n,n), mat(n,n), vec(n))
call random_number(y)
call random_number(C)
! Get the count rate
call system_clock(count_rate=count_rate)
! Original Aproach
call system_clock(iTime1)
res = 0.d0
do i=1, n
do j=1, n
res(:) = res(:) + y(i) * y(j) * C(:, j, i)
end do
end do
call system_clock(iTime2)
print *,'Orig: ',real(iTime2-iTime1)/real(count_rate)
! Original Aproach, DAXPY
call system_clock(iTime1)
resC = 0.d0
do i=1, n
do j=1, n
call daxpy(n, y(i)*y(j), C(:,j,i), 1, resC, 1)
end do
end do
call system_clock(iTime2)
print *,'Orig (DAXPY): ',real(iTime2-iTime1)/real(count_rate)
! print *,maxval( abs(resC-res) )
! Re-order
do i=1,n
C2(:,:,i) = C(i,:,:)
enddo !i
! sum((YY^t)C)
call system_clock(iTime1)
call dgemm('N','N',n,n,1,1.d0,y,n,y,1,0.d0,mat,n)
nn=n*n
do i=1,n
resC(i) = ddot( nn, mat, 1, C2(:,:,i), 1 )
enddo !i
call system_clock(iTime2)
print *,'sum((YY^t)C): ',real(iTime2-iTime1)/real(count_rate)
! print *,maxval( abs(resC-res) )
! sum(Y(Y^tC))
call system_clock(iTime1)
do i=1,n
call dgemm('N','N',1,n,n,1.d0,y,1,C2(:,:,i),n,0.d0,vec,1)
resC(i) = ddot( n, y, 1, vec, 1 )
enddo !i
call system_clock(iTime2)
print *,'sum(Y(Y^tC)): ',real(iTime2-iTime1)/real(count_rate)
! print *,maxval( abs(resC-res) )
end program test