Uyuşukluk nasıl Fortran rutinimden bu kadar hızlı olabilir?


82

Bir simülasyondan Sıcaklık dağılımını temsil eden 512 ^ 3 dizisi elde ediyorum (Fortran'da yazılmıştır). Dizi, boyutu yaklaşık 1 / 2G olan bir ikili dosyada saklanır. Bu dizinin minimum, maksimum ve ortalamasını bilmem gerekiyor ve yine de Fortran kodunu yakında anlamam gerekeceği için, onu denemeye karar verdim ve aşağıdaki çok kolay rutini buldum.

  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)

Bu, kullandığım makinedeki dosya başına yaklaşık 25 saniye sürüyor. Bu bana oldukça uzun geldi ve bu yüzden devam ettim ve Python'da şunları yaptım:

    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)

Şimdi, bunun elbette daha hızlı olmasını bekliyordum, ama gerçekten şaşırmıştım. Aynı koşullar altında bir saniyeden az sürer. Ortalama, Fortran rutinimin bulduğundan sapıyor (ki ben de 128-bitlik kaymalarla çalıştırdım, bu yüzden ona bir şekilde daha çok güveniyorum), ancak sadece 7. önemli basamakta ya da öylesine.

Uyuşuk nasıl bu kadar hızlı olabilir? Demek istediğim, bu değerleri bulmak için bir dizinin her girişine bakmanız gerekiyor, değil mi? Fortran rutinimde çok daha uzun sürmesi için çok aptalca bir şey mi yapıyorum?

DÜZENLE:

Yorumlardaki soruları cevaplamak için:

  • Evet, Fortran rutinini de 32-bit ve 64-bit kayanlarla çalıştırdım, ancak performans üzerinde hiçbir etkisi olmadı.
  • iso_fortran_env128-bit float sağlayan kullandım .
  • 32-bit float kullanmak ortalamam biraz yanlış, bu yüzden hassasiyet gerçekten bir sorundur.
  • Her iki rutini de farklı dosyalarda farklı sırayla çalıştırdım, bu yüzden önbelleğe alma, sanırım karşılaştırmada adil olmalıydı?
  • Aslında MP'yi açmayı denedim, ancak aynı anda farklı konumlarda dosyadan okumayı denedim. Yorumlarınızı ve cevaplarınızı okuduktan sonra bu kulağa çok aptalca geliyor ve rutini çok daha uzun sürüyor. Dizi işlemlerini deneyebilirim ama belki bu gerekli bile olmayacak.
  • Dosyalar aslında 1 / 2G boyutunda, bu bir yazım hatasıydı, Teşekkürler.
  • Şimdi dizi uygulamasını deneyeceğim.

DÜZENLEME 2:

@Alexander Vogt ve @casey'nin cevaplarında önerdiklerini uyguladım ve bu kadar hızlı numpyama şimdi @Luaan'ın alabileceğimi belirttiği gibi bir hassaslık sorunum var. 32 bitlik bir kayan dizi kullanıldığında, hesaplanan ortalama sum% 20 indirimdir. Yapıyor

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

Sorunu çözer ancak hesaplama süresini artırır (çok fazla değil, fark edilir ölçüde). Bu sorunu aşmanın daha iyi bir yolu var mı? Dosyadaki single'ları doğrudan çiftlere okumanın bir yolunu bulamadım. Ve bundan nasıl numpykaçınılır?

Şimdiye kadarki tüm yardımlarınız için teşekkürler.


10
128 bitlik kaymalar olmadan Fortran rutinini denediniz mi? Bunları gerçekten destekleyen herhangi bir donanımın farkında değilim, bu yüzden yazılımda yapılması gerekiyor.
user2357112

4
Bir dizi kullanarak (ve özellikle bir milyar yerine bir okuma kullanarak) Fortran sürümünü denerseniz ne olur?
francescalus

9
Fortran'da dizi operatörlerini de kullanmayı düşündünüz mü? Ardından, deneyebilirsin minval(), maxval()ve sum()? Ayrıca, IO'yu Fortran'daki işlemlerle karıştırıyorsunuz, ancak Python'da değil - bu adil bir karşılaştırma değil ;-)
Alexander Vogt

4
Büyük bir dosya içeren bir şeyi karşılaştırırken, tüm çalıştırmalar için aynı şekilde önbelleğe alındığından emin olun.
Tom Zych

1
Ayrıca, hassasiyetin Fortran'da oldukça önemli olduğunu ve bunun bir bedeli olduğunu unutmayın. Fortran kodunuzla ilgili tüm bu bariz sorunları düzelttikten sonra bile, ekstra hassasiyet gerekli olabilir ve önemli bir hız kaybına neden olabilir.
Luaan

Yanıtlar:


111

Fortran uygulamanızın iki büyük eksikliği var:

  • IO ve hesaplamaları karıştırırsınız (ve dosya girişinden girişten okursunuz).
  • Vektör / matris işlemlerini kullanmazsınız.

Bu uygulama, sizinkiyle aynı işlemi gerçekleştiriyor ve makinemde 20 kat daha hızlı:

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

Buradaki fikir, tüm dosyayı tmptek seferde tek bir dizi halinde okumaktır . Sonra ben işlevlerini kullanabilirsiniz MAXVAL, MINVALve SUMdoğrudan dizi.


Doğruluk sorunu için: Basitçe çift kesinlik değerleri kullanarak ve dönüşümü anında

mean = sum(real(tmp, kind=kind(1.d0)))/real(gridsize**3, kind=kind(1.d0))

hesaplama süresini yalnızca marjinal olarak artırır. İşlemi öğe bazında ve dilimler halinde gerçekleştirmeyi denedim, ancak bu yalnızca varsayılan optimizasyon düzeyinde gereken süreyi artırdı.

Öğesinde -O3, eleman bazlı toplama, dizi işleminden ~% 3 ​​daha iyi performans gösterir. Çift ve tek hassas işlemler arasındaki fark, makinemde% 2'den az - ortalama olarak (bireysel çalışmalar çok daha fazla sapma gösterir).


İşte LAPACK kullanarak çok hızlı bir uygulama:

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

Bu SLANGE, matris sütunlarında tek duyarlıklı matris 1-norm kullanır . Çalışma zamanı, tek duyarlıklı dizi işlevlerini kullanan yaklaşımdan bile daha hızlıdır ve kesinlik sorununu göstermez.


4
Girdiyi hesaplamayla karıştırmak onu neden bu kadar yavaşlatıyor? İkisinin de tüm dosyayı okuması gerekir, bu darboğaz olacaktır. Ve eğer işletim sistemi önden okuma yaparsa, Fortran kodunun G / Ç için fazla beklemesi gerekmez.
Barmar

3
@Barmar Verilerin her seferinde önbellekte olup olmadığını kontrol etmek için hala fonksiyon çağrısı ek yüküne ve mantığına sahip olacaksınız.
Bakış

56

Numpy daha hızlı çünkü python'da çok daha verimli kod yazdınız (ve arka uçların çoğu optimize edilmiş Fortran ve C ile yazılmıştır) ve Fortran'da korkunç derecede verimsiz kod yazdınız.

Python kodunuza bakın. Tüm diziyi bir kerede yükler ve ardından bir dizi üzerinde çalışabilen işlevleri çağırırsınız.

Fortran kodunuza bakın. Her seferinde bir değer okursunuz ve onunla biraz dallanma mantığı yaparsınız.

Tutarsızlığınızın çoğu, Fortran'da yazdığınız parçalanmış IO'dur.

Fortran'ı python'u yazdığınız gibi yazabilirsiniz ve bu şekilde çok daha hızlı çalıştığını göreceksiniz.

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

Bu şekilde hesaplanan ortalama ile aynı hassasiyet elde mu numpybireyin .meançağrısı? Bununla ilgili bazı şüphelerim var.
Bakuriu

1
@Bakuriu Hayır, değil. Alexander Vogt'un cevabına ve soruyla ilgili düzenlemelerime bakın.
user35915
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.