2013-08-27 6 views
51

Cython documentation on typed memory views सूची एक टाइप किया स्मृति को देखने के लिए बताए के तीन तरीके:टाइप किए गए मेमोरी व्यू के लिए मेमोरी आवंटित करने का अनुशंसित तरीका क्या है?

    एक कच्चे सी सूचक से एक np.ndarray से एक cython.view.array से
  1. ,
  2. और

मान लें कि मैं बाहर से मेरी cython कार्य करने के लिए में पारित डेटा नहीं है, लेकिन इसके बजाय स्मृति को आबंटित और एक np.ndarray है, जो उन विकल्पों में से मैं चुना है के रूप में इसे वापस करना चाहते हैं? इसके अलावा लगता है कि है कि बफर के आकार एक संकलन समय निरंतर यानी मैं ढेर पर आवंटित नहीं कर सकता नहीं है, लेकिन 1.

3 विकल्प इसलिए कुछ इस तरह looke हैं विकल्प के लिए malloc करने की आवश्यकता होगी:

from libc.stdlib cimport malloc, free 
cimport numpy as np 
from cython cimport view 

np.import_array() 

def memview_malloc(int N): 
    cdef int * m = <int *>malloc(N * sizeof(int)) 
    cdef int[::1] b = <int[:N]>m 
    free(<void *>m) 

def memview_ndarray(int N): 
    cdef int[::1] b = np.empty(N, dtype=np.int32) 

def memview_cyarray(int N): 
    cdef int[::1] b = view.array(shape=(N,), itemsize=sizeof(int), format="i") 

मुझे आश्चर्य की बात यह है कि स्मृति आवंटन के लिए सभी तीन मामलों में Cython generates quite a lot of code, विशेष रूप से __Pyx_PyObject_to_MemoryviewSlice_dc_int पर कॉल करें। यह सुझाव देता है (और मैं यहां गलत हो सकता हूं, साइथन के आंतरिक कार्यों में मेरी अंतर्दृष्टि बहुत सीमित है) कि यह पहले पाइथन ऑब्जेक्ट बनाता है और फिर इसे स्मृति दृश्य में "रखता है", जो अनावश्यक ओवरहेड लगता है।

simple benchmark प्रकट नहीं करता है, जिसमें तीन तरीकों के बीच बहुत अंतर दिखाई देता है, 2. पतली मार्जिन द्वारा सबसे तेज़ होना।

तीन तरीकों में से कौन सा अनुशंसा की जाती है? या क्या कोई अलग, बेहतर विकल्प है?

अनुवर्ती प्रश्न: मैं फ़ंक्शन में उस स्मृति दृश्य के साथ काम करने के बाद परिणाम को np.ndarray के रूप में परिणाम देना चाहता हूं। क्या टाइप की गई मेमोरी सबसे अच्छी पसंद है या क्या मैं पहले ही ndarray बनाने के लिए पुराने बफर इंटरफेस का उपयोग करूँगा?

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32) 
+2

उत्कृष्ट सवाल, मुझे कुछ इसी तरह के बारे में आश्चर्य है। – AlexE

+0

आपका बेंचमार्क सबसे अच्छा जवाब है जिसे मैं जानता हूं। फॉलो अप प्रश्न का उत्तर देने के लिए, आप बस अपनी NumPy सरणी को सामान्य तरीके से घोषित कर सकते हैं (आपको पुराने प्रकार के इंटरफ़ेस का उपयोग भी नहीं करना पड़ता है) और फिर कुछ प्राप्त करने के लिए 'cdef int [:] arrview = arr' जैसे कुछ करें NumPy सरणी के लिए उपयोग की जाने वाली एक ही स्मृति का दृश्य। आप तेजी से अनुक्रमण के लिए दृश्य और साइथन कार्यों के बीच स्लाइस पास करने के लिए उपयोग कर सकते हैं जबकि अभी भी NumPy सरणी के माध्यम से NumPy कार्यों तक पहुंच है। जब आप पूरा कर लेंगे तो आप केवल NumPy सरणी वापस कर सकते हैं। – IanH

+0

वहाँ एक [अच्छा संबंधित प्रश्न है ...] (http://stackoverflow.com/q/18410342/832621) जहां आप देख सकते हैं कि np.empty धीमा हो सकता है ... –

उत्तर

56

उत्तर के लिए here देखें।

मूल विचार है कि आप चाहते हैं cpython.array.array और cpython.array.clone (नहींcython.array.*) है:

from cpython.array cimport array, clone 

# This type is what you want and can be cast to things of 
# the "double[:]" syntax, so no problems there 
cdef array[double] armv, templatemv 

templatemv = array('d') 

# This is fast 
armv = clone(templatemv, L, False) 

संपादित

ऐसा लगता है कि उस सूत्र में बेंचमार्क बकवास थे। यहाँ मेरी सेट है, मेरे समय के साथ:

# cython: language_level=3 
# cython: boundscheck=False 
# cython: wraparound=False 

import time 
import sys 

from cpython.array cimport array, clone 
from cython.view cimport array as cvarray 
from libc.stdlib cimport malloc, free 
import numpy as numpy 
cimport numpy as numpy 

cdef int loops 

def timefunc(name): 
    def timedecorator(f): 
     cdef int L, i 

     print("Running", name) 
     for L in [1, 10, 100, 1000, 10000, 100000, 1000000]: 
      start = time.clock() 
      f(L) 
      end = time.clock() 
      print(format((end-start)/loops * 1e6, "2f"), end=" ") 
      sys.stdout.flush() 

     print("μs") 
    return timedecorator 

print() 
print("INITIALISATIONS") 
loops = 100000 

@timefunc("cpython.array buffer") 
def _(int L): 
    cdef int i 
    cdef array[double] arr, template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cpython.array memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 
    cdef array template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cpython.array raw C type") 
def _(int L): 
    cdef int i 
    cdef array arr, template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("numpy.empty_like memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 
    template = numpy.empty((L,), dtype='double') 

    for i in range(loops): 
     arr = numpy.empty_like(template) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("malloc") 
def _(int L): 
    cdef int i 
    cdef double* arrptr 

    for i in range(loops): 
     arrptr = <double*> malloc(sizeof(double) * L) 
     free(arrptr) 

    # Prevents dead code elimination 
    str(arrptr[0]) 

@timefunc("malloc memoryview") 
def _(int L): 
    cdef int i 
    cdef double* arrptr 
    cdef double[::1] arr 

    for i in range(loops): 
     arrptr = <double*> malloc(sizeof(double) * L) 
     arr = <double[:L]>arrptr 
     free(arrptr) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cvarray memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 

    for i in range(loops): 
     arr = cvarray((L,),sizeof(double),'d') 

    # Prevents dead code elimination 
    str(arr[0]) 



print() 
print("ITERATING") 
loops = 1000 

@timefunc("cpython.array buffer") 
def _(int L): 
    cdef int i 
    cdef array[double] arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cpython.array memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cpython.array raw C type") 
def _(int L): 
    cdef int i 
    cdef array arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("numpy.empty_like memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = numpy.empty((L,), dtype='double') 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("malloc") 
def _(int L): 
    cdef int i 
    cdef double* arrptr = <double*> malloc(sizeof(double) * L) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arrptr[i] 

    free(arrptr) 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("malloc memoryview") 
def _(int L): 
    cdef int i 
    cdef double* arrptr = <double*> malloc(sizeof(double) * L) 
    cdef double[::1] arr = <double[:L]>arrptr 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    free(arrptr) 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cvarray memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = cvarray((L,),sizeof(double),'d') 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

आउटपुट:

INITIALISATIONS 
Running cpython.array buffer 
0.100040 0.097140 0.133110 0.121820 0.131630 0.108420 0.112160 μs 
Running cpython.array memoryview 
0.339480 0.333240 0.378790 0.445720 0.449800 0.414280 0.414060 μs 
Running cpython.array raw C type 
0.048270 0.049250 0.069770 0.074140 0.076300 0.060980 0.060270 μs 
Running numpy.empty_like memoryview 
1.006200 1.012160 1.128540 1.212350 1.250270 1.235710 1.241050 μs 
Running malloc 
0.021850 0.022430 0.037240 0.046260 0.039570 0.043690 0.030720 μs 
Running malloc memoryview 
1.640200 1.648000 1.681310 1.769610 1.755540 1.804950 1.758150 μs 
Running cvarray memoryview 
1.332330 1.353910 1.358160 1.481150 1.517690 1.485600 1.490790 μs 

ITERATING 
Running cpython.array buffer 
0.010000 0.027000 0.091000 0.669000 6.314000 64.389000 635.171000 μs 
Running cpython.array memoryview 
0.013000 0.015000 0.058000 0.354000 3.186000 33.062000 338.300000 μs 
Running cpython.array raw C type 
0.014000 0.146000 0.979000 9.501000 94.160000 916.073000 9287.079000 μs 
Running numpy.empty_like memoryview 
0.042000 0.020000 0.057000 0.352000 3.193000 34.474000 333.089000 μs 
Running malloc 
0.002000 0.004000 0.064000 0.367000 3.599000 32.712000 323.858000 μs 
Running malloc memoryview 
0.019000 0.032000 0.070000 0.356000 3.194000 32.100000 327.929000 μs 
Running cvarray memoryview 
0.014000 0.026000 0.063000 0.351000 3.209000 32.013000 327.890000 μs 

(। "पुनरावृत्तियों" बेंचमार्क के लिए कारण यह है कि कुछ तरीकों को इस संबंध में आश्चर्यजनक रूप से अलग विशेषताएं हैं है)

प्रारंभिक गति के क्रम में:

malloc: यह एक कठोर दुनिया है, लेकिन यह तेज़ है।यदि आपको बहुत सी चीजों को आवंटित करने की आवश्यकता है और इसे बिना किसी अनुक्रमित और अनुक्रमित प्रदर्शन की आवश्यकता है, तो यह होना चाहिए। लेकिन आम तौर पर आप के लिए एक अच्छी शर्त है ...

cpython.array raw C type: ठीक है, यह तेज़ है। और यह सुरक्षित है। दुर्भाग्यवश यह अपने डेटा फ़ील्ड तक पहुंचने के लिए पायथन के माध्यम से जाता है। आप एक अद्भुत चाल का उपयोग कर इससे बच सकते हैं:

arr.data.as_doubles[i] 

जो सुरक्षा को हटाने के दौरान इसे मानक गति तक लाता है! यह अद्भुतmalloc के लिए प्रतिस्थापन बनाता है, मूल रूप से एक सुंदर संदर्भ-गणना संस्करण है!

cpython.array buffer: malloc के सेटअप समय में केवल तीन से चार गुना में आ रहा है, यह एक अद्भुत शर्त दिखता है। दुर्भाग्यवश इसमें महत्वपूर्ण ओवरहेड है (हालांकि boundscheck और wraparound निर्देशों की तुलना में कम है)। इसका मतलब है कि यह केवल पूर्ण सुरक्षा प्रकारों के खिलाफ वास्तव में प्रतिस्पर्धा करता है, लेकिन प्रारंभ करने वालों में से सबसे तेज़ है। आपकी पंसद।

cpython.array memoryview: यह अब प्रारंभ करने के लिए malloc से धीमी गति का क्रम है। यह एक शर्म की बात है, लेकिन यह तेजी से तेज़ करता है। यह मानक समाधान है जो मैं सुझाव दूंगा कि boundscheck या wraparound चालू हैं (जिस स्थिति में cpython.array buffer अधिक आकर्षक व्यापार हो सकता है)।

बाकी। ऑब्जेक्ट्स से जुड़ी कई मजेदार विधियों के कारण, कुछ भी मूल्यवान numpy है। हालांकि, यह है।

+0

उस व्यापक सर्वेक्षण और समर्थन के लिए धन्यवाद यह संख्या के साथ! – kynan

+1

महान जवाब! क्या मैं यह सोचने में सही हूं कि केवल शुद्ध 'मॉलोक' समाधान जीआईएल हासिल करने की आवश्यकता को पूरी तरह से रोक देगा? मैं समानांतर कार्यकर्ता धागे के भीतर बहुआयामी सरणी आवंटित करने के तरीकों में रूचि रखता हूं। –

+0

उन्हें आज़माएं और रिपोर्ट करें! – Veedrac

8

वीड्राक के उत्तर के अनुवर्ती के रूप में: का उपयोग के समर्थन के साथ पाइथन 2.7 के साथ वर्तमान में मेमोरी लीक का कारण बनता है। ऐसा लगता है कि नवंबर 2012 से एक पोस्ट में साइथन-उपयोगकर्ता मेलिंग सूची here पर इसका उल्लेख किया गया है। साइथन संस्करण 0.22 के साथ वेड्राक की बेंचमार्क स्क्रिप को पाइथन 2.7.6 और पायथन 2.7.9 दोनों के साथ चल रहा है। buffer या memoryview इंटरफ़ेस का उपयोग करते हुए cpython.array प्रारंभ करते समय बड़ी मेमोरी लीक। पायथन 3.4 के साथ स्क्रिप्ट चलाने पर कोई मेमोरी लीक नहीं होती है। मैंने साइथन डेवलपर्स मेलिंग सूची में इस पर एक बग रिपोर्ट दायर की है।

संबंधित मुद्दे