मेरे फोर्ट्रान रूटीन की तुलना में सुपीरियर इतना तेज कैसे हो सकता है?


82

मुझे एक सिमुलेशन (फोरट्रान में लिखित) से तापमान वितरण का प्रतिनिधित्व करते हुए एक 512 ^ 3 सरणी मिलती है। सरणी एक बाइनरी फ़ाइल में संग्रहीत है जो आकार में लगभग 1 / 2G है। मुझे इस सरणी का न्यूनतम, अधिकतम और मतलब जानना होगा और जैसा कि मुझे जल्द ही फोरट्रान कोड को समझने की आवश्यकता होगी, मैंने इसे एक बार देने का फैसला किया और निम्नलिखित बहुत आसान दिनचर्या के साथ आया।

  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 सेकंड लेता है। इसने मुझे लंबे होने के नाते मारा और इसलिए मैंने आगे बढ़कर पायथन में निम्नलिखित काम किया:

    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)

अब, मुझे उम्मीद थी कि यह तेजी से होगा, लेकिन मैं वास्तव में उड़ा दिया गया था। यह समान परिस्थितियों में एक सेकंड से भी कम समय लेता है। इसका मतलब मेरे फोरट्रान रूटीन से मिलता है (जिसे मैं 128-बिट फ़्लोट के साथ चलाता था, इसलिए मैं किसी तरह इस पर अधिक विश्वास करता हूं) लेकिन केवल 7 वें महत्वपूर्ण अंक पर।

सुन्न इतनी तेजी से कैसे हो सकता है? मेरा मतलब है कि आपको इन मूल्यों को खोजने के लिए किसी सरणी की प्रत्येक प्रविष्टि को देखना होगा, है ना? क्या मैं अपने फोरट्रान रूटीन में कुछ ज्यादा ही बेवकूफी कर रहा हूं ताकि इसे अधिक समय लग सके?

संपादित करें:

टिप्पणियों में सवालों के जवाब देने के लिए:

  • हां, मैंने भी 32-बिट और 64-बिट फ्लोट्स के साथ फोरट्रान रूटीन को चलाया, लेकिन इसका प्रदर्शन पर कोई प्रभाव नहीं पड़ा।
  • मैंने उपयोग किया है iso_fortran_envजो 128-बिट फ़्लोट प्रदान करता है।
  • 32-बिट फ्लोट्स का उपयोग करना, मेरा मतलब काफी हद तक बंद है, लेकिन सटीक वास्तव में एक मुद्दा है।
  • मैंने अलग-अलग क्रम में अलग-अलग फ़ाइलों पर दोनों रूटीन चलाए, इसलिए मेरे अनुमान की तुलना में कैशिंग उचित होना चाहिए था?
  • मैंने वास्तव में सांसद को खोलने की कोशिश की, लेकिन एक ही समय में विभिन्न पदों पर फ़ाइल से पढ़ने के लिए। आपकी टिप्पणियों और उत्तरों को पढ़ने के बाद लगता है कि यह वास्तव में बेवकूफी भरा है और इसने दिनचर्या को बहुत लंबा बना दिया है। मैं इसे सरणी के संचालन पर एक कोशिश दे सकता हूं, लेकिन शायद यह भी आवश्यक नहीं होगा।
  • फ़ाइलें वास्तव में आकार में 1 / 2G हैं, यह एक टाइपो था, धन्यवाद।
  • मैं अब सरणी कार्यान्वयन की कोशिश करूंगा।

संपादित करें 2:

मैंने उनके जवाबों में @Alexander Vogt और @casey को लागू करने का सुझाव दिया है, और यह उतनी ही तेजी से है, numpyलेकिन अब मेरे पास एक सटीक समस्या है क्योंकि @Luaan ने कहा कि मुझे मिल सकता है। एक 32-बिट फ्लोट सरणी का उपयोग करके गणना sum20% से कम है। करते हुए

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

समस्या हल करता है, लेकिन कंप्यूटिंग समय बढ़ाता है (बहुत अधिक नहीं, बल्कि विशेष रूप से)। क्या इस मुद्दे को हल करने का एक बेहतर तरीका है? मुझे फ़ाइल से एकल को डबल्स में सीधे पढ़ने का कोई तरीका नहीं मिला। और इससे कैसे numpyबचा जाता है?

अभी तक मदद करने के लिए सभी को धन्यवाद।


10
क्या आपने 128-बिट फ़्लोट्स के बिना फोरट्रान दिनचर्या की कोशिश की? मैं किसी भी हार्डवेयर के बारे में नहीं जानता जो वास्तव में उन का समर्थन करता है, इसलिए उन्हें सॉफ्टवेयर में करना होगा।
user2357112

4
क्या होगा यदि आप एक सरणी (और विशेष रूप से एक अरब के बजाय एक पढ़ने का उपयोग करके) फोरट्रान संस्करण की कोशिश करते हैं?
फ्रांसेकल्कस

9
क्या आपने फोरट्रान में सरणी ऑपरेटरों का उपयोग करने पर भी विचार किया था? उसके बाद, आप की कोशिश कर सकते minval(), maxval()और sum()? इसके अलावा, आप फोर्टेन में संचालन के साथ आईओ का मिश्रण कर रहे हैं, लेकिन पायथन में नहीं - यह एक उचित अवरोध नहीं है ;-)
अलेक्जेंडर वोग्ट

4
जब किसी बड़ी फ़ाइल को बेंचमार्किंग करते हैं, तो सुनिश्चित करें कि यह सभी रन के लिए समान है।
टॉम ज़िक

1
यह भी ध्यान दें कि सटीक फोरट्रान में एक बड़ा सौदा है, और यह एक लागत पर आता है। अपने फोरट्रान कोड के साथ उन सभी स्पष्ट मुद्दों को ठीक करने के बाद भी, यह बहुत अच्छी तरह से हो सकता है कि अतिरिक्त परिशुद्धता की आवश्यकता है और एक महत्वपूर्ण गति हानि का कारण होगा।
लुआं नोव

जवाबों:


110

आपका फोरट्रान कार्यान्वयन दो बड़ी कमियों से ग्रस्त है:

  • आप 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में पढ़ने के लिए है । फिर, मैं कार्यों का उपयोग कर सकते हैं 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

यह SLANGEमैट्रिक्स कॉलम पर एकल परिशुद्धता मैट्रिक्स 1-मानक का उपयोग करता है । रन-टाइम एकल परिशुद्धता सरणी फ़ंक्शंस का उपयोग करते हुए दृष्टिकोण से भी तेज है - और सटीक मुद्दा नहीं दिखाता है।


4
गणना के साथ मिक्सिंग इनपुट इसे धीमा क्यों करता है? उन दोनों को पूरी फाइल पढ़नी होगी, यही अड़चन होगी। और अगर OS ​​रीडहेड करता है, तो फोरट्रान कोड को I / O के लिए ज्यादा इंतजार नहीं करना चाहिए।
बरमार

3
@Barmar आपके पास अभी भी फ़ंक्शन कॉल ओवरहेड और तर्क की जाँच के लिए होगा कि डेटा हर बार कैश में है या नहीं।
ओवरव्यू

55

सुन्न तेजी से है क्योंकि आपने अजगर में बहुत अधिक कुशल कोड लिखा था (और सुन्न बैकेंड का बहुत कुछ अनुकूलित फोरट्रान और सी में लिखा गया है) और फोरट्रान में बहुत अक्षम कोड है।

अपने अजगर कोड को देखो। आप एक बार में पूरे सरणी को लोड करते हैं और फिर फ़ंक्शन को कॉल करते हैं जो एक सरणी पर काम कर सकते हैं।

अपने फोरट्रान कोड को देखें। आप एक बार में एक मूल्य पढ़ते हैं और इसके साथ कुछ शाखीय तर्क करते हैं।

आपकी विसंगति का अधिकांश हिस्सा वह खंडित IO है जिसे आपने फोरट्रान में लिखा है।

आप फोर्ट्रान को उसी तरह लिख सकते हैं जैसे आपने अजगर को लिखा था और आप पाएंगे कि यह उस तरह से बहुत तेजी से चलता है।

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

मतलब इस तरह से गणना की रूप में एक ही सटीक प्राप्त करता है numpyके .meanकॉल? मुझे उस पर कुछ संदेह है।
बकुरीउ

1
@ बकुरीउ नहीं, यह नहीं है। अलेक्जेंडर वोग्ट का जवाब और सवाल पर मेरे संपादन देखें।
user35915
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.