簡體   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