繁体   English   中英

numpy怎么能比我的Fortran例程快得多?

[英]How can numpy be so much faster than my Fortran routine?

我得到一个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例行程中做了一件非常愚蠢的事情,因为它花了这么长时间吗?

编辑:

要回答评论中的问题:

  • 是的,我也使用32位和64位浮点运行Fortran例程,但它对性能没有影响。
  • 我使用了iso_fortran_env ,它提供了128位浮点数。
  • 使用32位浮点数我的意思是相当多,所以精度确实是一个问题。
  • 我以不同的顺序在不同的文件上运行这两个例程,所以缓存在比较中应该是公平的吗?
  • 我实际上试过打开MP,但同时从不同位置的文件中读取。 阅读完你的评论和答案后,这听起来真的很愚蠢,这使得日常工作也需要更长的时间。 我可能试一试数组操作,但也许甚至不需要。
  • 文件实际上是1 / 2G大小,这是一个错字,谢谢。
  • 我现在将尝试数组实现。

编辑2:

我实现了@Alexander Vogt和@casey在他们的答案中提出的建议,它和numpy一样快,但现在我有一个精确的问题,因为@Luaan指出我可能会得到。 使用32位浮点数组,由sum计算的平均值为20%。

...
real,allocatable :: tmp (:,:,:)
double precision,allocatable :: tmp2(:,:,:)
...
tmp2=tmp
mean=sum(tmp2)/size(tmp)
...

解决了这个问题但增加了计算时间(不是很多,但显着)。 有没有更好的方法来解决这个问题? 我找不到从文件中直接读单打的方法。 numpy如何避免这种情况?

感谢目前为止所有的帮助。

您的Fortran实现存在两个主要缺点:

  • 您混合IO和计算(并通过条目从文件条目读取)。
  • 您不使用矢量/矩阵运算。

此实现确实执行与您相同的操作,并且在我的机器上运行速度提高了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 然后,我可以直接在数组上使用MAXVALMINVALSUM函数。


对于准确性问题:只需使用双精度值并即时进行转换

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范数SLANGE 运行时甚至比使用单精度数组函数的方法更快 - 并且没有显示精度问题。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM