सबसे तेजी से एक संख्यात्मक संख्यात्मक सरणी बढ़ने का तरीका


80

आवश्यकताएँ:

  • मुझे डेटा से बड़े पैमाने पर एक सरणी विकसित करने की आवश्यकता है।
  • मैं बिना किसी गारंटी के आकार (लगभग 100-200) का अनुमान लगा सकता हूं कि सरणी हर बार फिट होगी
  • एक बार जब यह अपने अंतिम आकार में हो जाता है, तो मुझे इस पर संख्यात्मक संगणना करने की आवश्यकता होती है, इसलिए मैं अंततः 2-डी के बराबर सरणी प्राप्त करना पसंद करूंगा।
  • गति महत्वपूर्ण है। उदाहरण के रूप में, 300 फ़ाइलों में से एक के लिए, अपडेट () विधि को 45 मिलियन बार (150s या तो लेता है) और अंतिम रूप () विधि को 500k बार (कुल 106s लेता है) कहा जाता है ... कुल 250s या ऐसा।

यहाँ मेरा कोड है:

def __init__(self):
    self.data = []

def update(self, row):
    self.data.append(row)

def finalize(self):
    dx = np.array(self.data)

मैंने जिन अन्य चीजों की कोशिश की उनमें निम्नलिखित कोड शामिल हैं ... लेकिन यह वायाए धीमे है।

def class A:
    def __init__(self):
        self.data = np.array([])

    def update(self, row):
        np.append(self.data, row)

    def finalize(self):
        dx = np.reshape(self.data, size=(self.data.shape[0]/5, 5))

यहाँ एक योजनाबद्ध है कि इसे कैसे कहा जाता है:

for i in range(500000):
    ax = A()
    for j in range(200):
         ax.update([1,2,3,4,5])
    ax.finalize()
    # some processing on ax

2
क्या यह समाप्त होने से पहले एक सुव्यवस्थित सरणी होने की आवश्यकता है? यदि नहीं, तो सूचियों की एक सूची का उपयोग करें और फिर समाप्त होने पर परिवर्तित करें।
एंड्रयू जफ

1
@AndrewJaffe क्या सूचियों की सूचियां खस की स्मृति दक्षता से मेल खाती हैं?
AturSams

जवाबों:


96

मैंने टाइमिंग के साथ कुछ अलग चीजें ट्राई कीं।

import numpy as np
  1. आपके द्वारा बताई गई विधि धीमी है: (32.094 सेकंड)

    class A:
    
        def __init__(self):
            self.data = np.array([])
    
        def update(self, row):
            self.data = np.append(self.data, row)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(self.data.shape[0]/5, 5))
    
  2. नियमित ओल पायथन सूची: (0.308 सेकंड)

    class B:
    
        def __init__(self):
            self.data = []
    
        def update(self, row):
            for r in row:
                self.data.append(r)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(len(self.data)/5, 5))
    
  3. एक सरणी सूची को संख्यात्मक रूप से लागू करने की कोशिश करना: (0.362 सेकंड)

    class C:
    
        def __init__(self):
            self.data = np.zeros((100,))
            self.capacity = 100
            self.size = 0
    
        def update(self, row):
            for r in row:
                self.add(r)
    
        def add(self, x):
            if self.size == self.capacity:
                self.capacity *= 4
                newdata = np.zeros((self.capacity,))
                newdata[:self.size] = self.data
                self.data = newdata
    
            self.data[self.size] = x
            self.size += 1
    
        def finalize(self):
            data = self.data[:self.size]
            return np.reshape(data, newshape=(len(data)/5, 5))
    

और यह है कि मैं इसे समय पर:

x = C()
for i in xrange(100000):
    x.update([i])

तो ऐसा लगता है कि नियमित रूप से पुराने पायथन की सूची बहुत अच्छी है;)


1
मुझे लगता है कि तुलना 60M अपडेट के साथ स्पष्ट है और 500K कॉल को अंतिम रूप देता है। ऐसा लगता है कि आपने इस उदाहरण में अंतिम रूप नहीं दिया है।
फोडन

1
@fodon मैंने वास्तव में कॉल को अंतिम रूप दिया - एक बार प्रति रन (इसलिए मुझे लगता है कि वास्तव में बहुत अधिक प्रभाव नहीं है)। लेकिन इससे मुझे लगता है कि शायद मुझे गलतफहमी हो गई है कि आपका डेटा कैसे बढ़ रहा है: यदि आपको अपडेट पर 60M मिलता है, तो मैं सोच रहा था कि यह अगले फाइनल के लिए कम से कम 60M डेटा प्रदान करेगा?
ओवेन

@Owen 60M और 500K मतलब 60 करोड़ और 500 हजार कॉल करने के लिए updateऔर finalizeक्रमशः। मेरे संशोधित समय को देखें जो कि 100: 1 के अनुपात का परीक्षण करता updateहैfinalize
प्रशांत कुमार

मैंने एक छोटी स्क्रिप्ट के साथ प्रश्न को अपडेट किया है (यह वाक्य रचना के लिए सही नहीं हो सकता है) कि यह कैसे काम करता है, इसका अंदाजा लगाने के लिए।
fodon

3
ध्यान दें कि जब आप स्मृति से बाहर चल रहे हों तो तीसरा विकल्प बेहतर है। दूसरे विकल्प में बहुत सारी मेमोरी की आवश्यकता होती है। कारण यह है कि पायथन की सूची मूल्यों के संदर्भ में सरणियाँ हैं, जबकि न्यूमपी के सरणियाँ मूल्यों के वास्तविक सरणियाँ हैं।
फेबियनियस

20

np.append () सरणी में सभी डेटा को हर बार कॉपी करते हैं, लेकिन सूची एक कारक (1.125) द्वारा क्षमता बढ़ती है। सूची तेज़ है, लेकिन मेमोरी उपयोग सरणी से बड़ा है। यदि आप स्मृति की परवाह करते हैं तो आप पायथन मानक पुस्तकालय के सरणी मॉड्यूल का उपयोग कर सकते हैं।

यहाँ इस विषय पर चर्चा है:

डायनेमिक ऐरे कैसे बनाएं


2
क्या उस कारक को बदलने का कोई तरीका है जिसके द्वारा सूची बढ़ती है?
फोडन

1
np.append () समय की खपत तत्वों की संख्या के साथ तेजी से बढ़ती है।
घड़ी ZHONG

1
^ रैखिक (यानी कुल संचित समय चतुष्कोण है), घातीय नहीं।
user1111929

15

ओवेन के पद में वर्ग घोषणाओं का उपयोग करते हुए, यहां अंतिम समय के कुछ प्रभाव के साथ एक संशोधित समय है।

संक्षेप में, मुझे एक कार्यान्वयन प्रदान करने के लिए क्लास सी लगता है जो मूल पोस्ट में विधि की तुलना में 60x से अधिक तेज है। (पाठ की दीवार के लिए माफी)

मैंने जो फ़ाइल उपयोग की है:

#!/usr/bin/python
import cProfile
import numpy as np

# ... class declarations here ...

def test_class(f):
    x = f()
    for i in xrange(100000):
        x.update([i])
    for i in xrange(1000):
        x.finalize()

for x in 'ABC':
    cProfile.run('test_class(%s)' % x)

अब, परिणामी समय:

ए:

     903005 function calls in 16.049 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000   16.049   16.049 <string>:1(<module>)
100000    0.139    0.000    1.888    0.000 fromnumeric.py:1043(ravel)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
100000    0.322    0.000   14.424    0.000 function_base.py:3466(append)
100000    0.102    0.000    1.623    0.000 numeric.py:216(asarray)
100000    0.121    0.000    0.298    0.000 numeric.py:286(asanyarray)
  1000    0.002    0.000    0.004    0.000 test.py:12(finalize)
     1    0.146    0.146   16.049   16.049 test.py:50(test_class)
     1    0.000    0.000    0.000    0.000 test.py:6(__init__)
100000    1.475    0.000   15.899    0.000 test.py:9(update)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
100000    0.126    0.000    0.126    0.000 {method 'ravel' of 'numpy.ndarray' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
200001    1.698    0.000    1.698    0.000 {numpy.core.multiarray.array}
100000   11.915    0.000   11.915    0.000 {numpy.core.multiarray.concatenate}

बी:

     208004 function calls in 16.885 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.001    0.001   16.885   16.885 <string>:1(<module>)
  1000    0.025    0.000   16.508    0.017 fromnumeric.py:107(reshape)
  1000    0.013    0.000   16.483    0.016 fromnumeric.py:32(_wrapit)
  1000    0.007    0.000   16.445    0.016 numeric.py:216(asarray)
     1    0.000    0.000    0.000    0.000 test.py:16(__init__)
100000    0.068    0.000    0.080    0.000 test.py:19(update)
  1000    0.012    0.000   16.520    0.017 test.py:23(finalize)
     1    0.284    0.284   16.883   16.883 test.py:50(test_class)
  1000    0.005    0.000    0.005    0.000 {getattr}
  1000    0.001    0.000    0.001    0.000 {len}
100000    0.012    0.000    0.012    0.000 {method 'append' of 'list' objects}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.020    0.000    0.020    0.000 {method 'reshape' of 'numpy.ndarray' objects}
  1000   16.438    0.016   16.438    0.016 {numpy.core.multiarray.array}

सी:

     204010 function calls in 0.244 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.244    0.244 <string>:1(<module>)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
     1    0.000    0.000    0.000    0.000 test.py:27(__init__)
100000    0.082    0.000    0.170    0.000 test.py:32(update)
100000    0.087    0.000    0.088    0.000 test.py:36(add)
  1000    0.002    0.000    0.005    0.000 test.py:46(finalize)
     1    0.068    0.068    0.243    0.243 test.py:50(test_class)
  1000    0.000    0.000    0.000    0.000 {len}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
     6    0.001    0.000    0.001    0.000 {numpy.core.multiarray.zeros}

अपडेट के द्वारा क्लास ए को नष्ट कर दिया जाता है, क्लास बी को अंतिम रूप दिया जाता है। क्लास सी दोनों के सामने मजबूत है।


अद्यतन एक बार किया जाता है फिर अंतिम रूप से एक बार कहा जाता है। यह पूरी प्रक्रिया एम बार की जाती है (अन्यथा अंतिम रूप देने के लिए कोई डेटा नहीं है)। इसके अलावा, जब मूल पोस्ट के साथ तुलना की जाती है ... तो क्या आपका मतलब है पहला (array.append + numpy रूपांतरण) या (numpy.append + reshape)?
फोडन

1
cProfile। यह पहली आयात और मेरे कोड स्निपेट में अंतिम पंक्ति है।
प्रशांत कुमार

5

फ़ंक्शन में एक बड़ा प्रदर्शन अंतर है जिसे आप अंतिम रूप देने के लिए उपयोग करते हैं। निम्नलिखित कोड पर विचार करें:

N=100000
nruns=5

a=[]
for i in range(N):
    a.append(np.zeros(1000))

print "start"

b=[]
for i in range(nruns):
    s=time()
    c=np.vstack(a)
    b.append((time()-s))
print "Timing version vstack ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c1=np.reshape(a,(N,1000))
    b.append((time()-s))

print "Timing version reshape ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c2=np.concatenate(a,axis=0).reshape(-1,1000)
    b.append((time()-s))

print "Timing version concatenate ",np.mean(b)

print c.shape,c2.shape
assert (c==c2).all()
assert (c==c1).all()

समवर्ती का उपयोग करना पहले संस्करण की तुलना में दोगुना और दूसरे संस्करण की तुलना में 10 गुना अधिक तेज लगता है।

Timing version vstack  1.5774928093
Timing version reshape  9.67419199944
Timing version concatenate  0.669512557983

1

यदि आप सूची संचालन के साथ प्रदर्शन में सुधार करना चाहते हैं, तो पुस्तकालय देखने के लिए एक नज़र रखें। यह अजगर सूची और अन्य संरचनाओं का एक अनुकूलित कार्यान्वयन है।

मैंने इसे अभी तक बेंचमार्क नहीं किया था, लेकिन उनके पृष्ठ के परिणाम आशाजनक प्रतीत होते हैं।

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