我正在尝试计算类似于Fortran中加权矩阵内积的东西。我用于计算内积的当前脚本如下
! --> In
real(kind=8), intent(in), dimension(ni, nj, nk, nVar) :: U1, U2
real(kind=8), intent(in), dimension(ni, nj, nk) :: intW
! --> Out
real(kind=8), intent(out) :: innerProd
! --> Local
integer :: ni, nj, nk, nVar, iVar
! --> Computing inner product
do iVar = 1, nVar
innerProd = innerProd + sum(U1(:,:,:,iVar)*U2(:,:,:,iVar)*intW)
enddo
但我发现我目前使用的上述脚本效率不高。可以使用NumPy在Python中执行相同的操作,如下所示,
import numpy as np
import os
# --> Preventing numpy from multi-threading
os.environ['OPENBLAS_NUM_THREADS'] = '1'
os.environ['MKL_NUM_THREADS'] = '1'
innerProd = 0
# --> Toy matrices
U1 = np.random.random((ni,nj,nk,nVar))
U2 = np.random.random((ni,nj,nk,nVar))
intW = np.random.random((ni,nj,nk))
# --> Reshaping
U1 = np.reshape(np.ravel(U1), (ni*nj*nk, nVar))
U2 = np.reshape(np.ravel(U1), (ni*nj*nk, nVar))
intW = np.reshape(np.ravel(intW), (ni*nj*nk))
# --> Calculating inner product
for iVar in range(nVar):
innerProd = innerProd + np.dot(U1[:, iVar], U2[:, iVar]*intW)
使用Numpy的第二种方法似乎比使用Fortran的方法快得多。对于ni = nj = nk = nVar = 130
的特定情况,两种方法所用的时间如下
fortran_time = 25.8641 s
numpy_time = 6.8924 s
我尝试使用BLAS中的ddot
改进我的Fortran代码,如下所示,
do iVar = 1, nVar
do k = 1, nk
do j = 1, nj
innerProd = innerProd + ddot(ni, U1(:,j,k,iVar), 1, U2(:,j,k,iVar)*intW(:,j,k), 1)
enddo
enddo
enddo
但是时间没有太大的改善。上述方法针对ni = nj = nk = nVar = 130
的情况所花费的时间是~24s
。 (我忘了提到我使用'-O2'选项编译了Fortran代码以优化性能。)
不幸的是,Fortran中没有用于逐元素矩阵乘法的BLAS函数。而且我不想在Fortran中使用reshape,因为与Fortran中的python重塑不同会导致将我的数组复制到一个新的数组,导致更多的RAM使用。
有没有办法加快Fortran的性能,以便接近Numpy的性能?
答案 0 :(得分:2)
您可能无法计算您认为的时间安排。这是一个完整的fortran例子
$ gfortran -O2 -o inner_product inner_product.f90
$ time ./inner_product
allocation time(s): 3.00000E-05
random init time (s): 5.73293E+00
Sum: 3.57050E+07 time(s): 5.69066E-01
real 0m6.465s
user 0m4.634s
sys 0m1.798s
输出:
const loadScript = (source, beforeEl, async = true, defer = true) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
const prior = beforeEl || document.getElementsByTagName('script')[0];
script.async = async;
script.defer = defer;
function onloadHander(_, isAbort) {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
if (isAbort) { reject(); } else { resolve(); }
}
}
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.src = source;
prior.parentNode.insertBefore(script, prior);
});
}
在这个fortran代码中,计算内部产品的运行时间不到10%。你如何/你的时间是非常重要的。你确定你在fortran和python版本中计时相同吗?你确定你只是计算inner_product计算的时间吗?
答案 1 :(得分:1)
这可以避免制作任何副本。 (注意blas ddot
方法仍需要为元素明确的产品制作副本)
subroutine dot3(n,a,b,c,result)
implicit none
real(kind=..) a(*),b(*),c(*),result
integer i,n
result=0
do i=1,n
result=result+a(i)*b(i)*c(i)
enddo
end
dot3
是外部的,意味着模块/包含构造中的不是。 kind
显然应与主要声明匹配。
在主要代码中:
innerprod=0
do iVar = 1, nVar
call dot3(ni*nj*nk, U1(1,1,1,iVar),U2(1,1,1,iVar),intW,result)
innerProd=innerProd+result
enddo
答案 2 :(得分:1)
比较Numpy和Fortran代码时,我有相同的观察结果。
差异竟然是BLAS的版本,我发现使用netlib中的DGEMM
与循环类似,并且比OpenBLAS慢大约三倍(请参阅this答案中的配置文件) 。
对我来说,最令人惊讶的是,OpenBLAS提供的代码比仅编译Fortran三重嵌套循环要快得多。看来这就是GotoBLAS的全部要点,它是用处理器结构的汇编代码手写的。
即使定时正确,排序正确,避免复制并使用每个优化标志(在gfortran中),性能仍然比OpenBLAS慢大约三倍。我没有尝试过ifort或pgi,但是我想知道这是否解释了@kvantour提出的“对我来说0.6s循环完成”的评论(请注意,在某些实现中,固有的matmul被BLAS取代了)。