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

时间:2015-11-15 19:10:57

标签: python arrays performance numpy fortran

我得到一个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如何避免这种情况?

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

2 个答案:

答案 0 :(得分:110)

您的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-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