我得到一个512 ^ 3数组,表示模拟的温度分布(用Fortran编写)。该数组存储在大小约为1 / 2G的二进制文件中。我需要知道这个数组的最小值,最大值和平均值,因为我很快就需要了解Fortran代码,我决定试一试,并提出以下非常简单的例程。
integer gridsize,unit,j
real mini,maxi
double precision mean
gridsize=512
unit=40
open(unit=unit,file='T.out',status='old',access='stream',&
form='unformatted',action='read')
read(unit=unit) tmp
mini=tmp
maxi=tmp
mean=tmp
do j=2,gridsize**3
read(unit=unit) tmp
if(tmp>maxi)then
maxi=tmp
elseif(tmp<mini)then
mini=tmp
end if
mean=mean+tmp
end do
mean=mean/gridsize**3
close(unit=unit)
我使用的机器上每个文件大约需要25秒。这让我觉得很长,所以我继续在Python中做了以下事情:
import numpy
mmap=numpy.memmap('T.out',dtype='float32',mode='r',offset=4,\
shape=(512,512,512),order='F')
mini=numpy.amin(mmap)
maxi=numpy.amax(mmap)
mean=numpy.mean(mmap)
现在,我预计这会更快,但我真的被吹走了。在相同条件下只需不到一秒钟。平均值偏离我的Fortran例程找到的那个(我也使用128位浮点运行,所以我不知何故更多地信任它),但仅限于第7位有效数字左右。
numpy怎么这么快?我的意思是你必须查看数组的每个条目才能找到这些值,对吧?我在Fortran程序中做了一件非常愚蠢的事情,因为它花了这么长时间吗?
修改
回答评论中的问题:
iso_fortran_env
提供了128位浮点数。编辑2:
我实现了@Alexander Vogt和@casey在他们的答案中提出的建议,它和numpy
一样快,但现在我有一个精确的问题,因为@Luaan指出我可能会得到。使用32位浮点数组,由sum
计算的平均值为20%。做
...
real,allocatable :: tmp (:,:,:)
double precision,allocatable :: tmp2(:,:,:)
...
tmp2=tmp
mean=sum(tmp2)/size(tmp)
...
解决了这个问题,但增加了计算时间(不是很多,但显着)。
有没有更好的方法来解决这个问题?我找不到从文件中直接读单打的方法。
numpy
如何避免这种情况?
感谢目前为止提供的所有帮助。
答案 0 :(得分:110)
您的Fortran实施存在两个主要缺点:
此实现执行的操作与您的操作相同,并且在我的计算机上运行速度提高了20倍:
program test
integer gridsize,unit
real mini,maxi,mean
real, allocatable :: tmp (:,:,:)
gridsize=512
unit=40
allocate( tmp(gridsize, gridsize, gridsize))
open(unit=unit,file='T.out',status='old',access='stream',&
form='unformatted',action='read')
read(unit=unit) tmp
close(unit=unit)
mini = minval(tmp)
maxi = maxval(tmp)
mean = sum(tmp)/gridsize**3
print *, mini, maxi, mean
end program
这个想法是一次性将整个文件读入一个数组tmp
。然后,我可以直接在数组上使用函数MAXVAL
,MINVAL
和SUM
。
对于准确性问题:只需使用双精度值并即时进行转换
mean = sum(real(tmp, kind=kind(1.d0)))/real(gridsize**3, kind=kind(1.d0))
仅略微增加计算时间。我尝试在切片中执行元素操作,但这只会增加默认优化级别所需的时间。
在-O3
,元素添加比阵列操作好大约3%。在我的机器上,双精度和单精度操作之间的差异小于2% - 平均而言(个别运行偏差更多)。
这是使用LAPACK的非常快速的实现:
program test
integer gridsize,unit, i, j
real mini,maxi
integer :: t1, t2, rate
real, allocatable :: tmp (:,:,:)
real, allocatable :: work(:)
! double precision :: mean
real :: mean
real :: slange
call system_clock(count_rate=rate)
call system_clock(t1)
gridsize=512
unit=40
allocate( tmp(gridsize, gridsize, gridsize), work(gridsize))
open(unit=unit,file='T.out',status='old',access='stream',&
form='unformatted',action='read')
read(unit=unit) tmp
close(unit=unit)
mini = minval(tmp)
maxi = maxval(tmp)
! mean = sum(tmp)/gridsize**3
! mean = sum(real(tmp, kind=kind(1.d0)))/real(gridsize**3, kind=kind(1.d0))
mean = 0.d0
do j=1,gridsize
do i=1,gridsize
mean = mean + slange('1', gridsize, 1, tmp(:,i,j),gridsize, work)
enddo !i
enddo !j
mean = mean / gridsize**3
print *, mini, maxi, mean
call system_clock(t2)
print *,real(t2-t1)/real(rate)
end program
这在矩阵列上使用单精度矩阵1-norm SLANGE
。运行时甚至比使用单精度数组函数的方法更快 - 并且没有显示精度问题。
答案 1 :(得分:55)
numpy更快,因为你在python中编写了更高效的代码(并且大部分numpy后端是用优化的Fortran和C编写的)和Fortran中非常低效的代码。
看看你的python代码。您可以立即加载整个数组,然后调用可以在数组上运行的函数。
看看你的fortran代码。您一次读取一个值并使用它执行一些分支逻辑。
您的大部分差异是您在Fortran中编写的碎片IO。
你可以像编写python一样编写Fortran,你会发现它的运行速度要快得多。
program test
implicit none
integer :: gridsize, unit
real :: mini, maxi, mean
real, allocatable :: array(:,:,:)
gridsize=512
allocate(array(gridsize,gridsize,gridsize))
unit=40
open(unit=unit, file='T.out', status='old', access='stream',&
form='unformatted', action='read')
read(unit) array
maxi = maxval(array)
mini = minval(array)
mean = sum(array)/size(array)
close(unit)
end program test