2017-07-16 11 views
5

(साथ NxM 1K < = एन < = 20K & 10K < = एम < = 200K), मैं अक्सर Numpy पास करनी होगी नौकरी पाने के लिए साइथन के माध्यम से सी ++ के लिए matrices और यह प्रतिलिपि के बिना & अपेक्षित के रूप में काम करता है।नकल के बिना Cython के माध्यम से Numpy को पासिंग सी ++ वेक्टर और बड़े मैट्रिक्स प्रसंस्करण के साथ काम स्वचालित रूप से स्मृति प्रबंधन की देखभाल

हालांकि, कई बार जब मैं शुरू करने और C++ एक मैट्रिक्स preprocess और यह Numpy (अजगर 3.6) को पास किए जाने हैं। आइए मान लें कि मैट्रिस रैखिकृत हैं (इसलिए आकार एन * एम है और यह 1 डी मैट्रिक्स है - कॉल/पंक्ति प्रमुख यहां कोई फर्क नहीं पड़ता)। यहां दी गई जानकारी के बाद: exposing C-computed arrays in Python without data copies & इसे C++ संगतता के लिए संशोधित करने के लिए, मैं सी ++ सरणी पास करने में सक्षम हूं।

समस्या है यदि मैं सरणी शुरू करने के बजाय std वेक्टर का उपयोग करना चाहता हूं, तो मुझे सेगमेंटेशन गलती मिल जाएगी। उदाहरण के लिए, निम्न फ़ाइलों पर विचार:

fast.h

#include <iostream> 
#include <vector> 

using std::cout; using std::endl; using std::vector; 
int* doit(int length); 

fast.cpp

#include "fast.h" 
int* doit(int length) { 
    // Something really heavy 
    cout << "C++: doing it fast " << endl; 

    vector<int> WhyNot; 

    // Heavy stuff - like reading a big file and preprocessing it 
    for(int i=0; i<length; ++i) 
     WhyNot.push_back(i); // heavy stuff 

    cout << "C++: did it really fast" << endl; 
    return &WhyNot[0]; // or WhyNot.data() 
} 

faster.pyx

cimport numpy as np 
import numpy as np 
from libc.stdlib cimport free 
from cpython cimport PyObject, Py_INCREF 

np.import_array() 

cdef extern from "fast.h": 
    int* doit(int length) 

cdef class ArrayWrapper: 
    cdef void* data_ptr 
    cdef int size 

    cdef set_data(self, int size, void* data_ptr): 
     self.data_ptr = data_ptr 
     self.size = size 

    def __array__(self): 
     print ("Cython: __array__ called") 
     cdef np.npy_intp shape[1] 
     shape[0] = <np.npy_intp> self.size 
     ndarray = np.PyArray_SimpleNewFromData(1, shape, 
               np.NPY_INT, self.data_ptr) 
     print ("Cython: __array__ done") 
     return ndarray 

    def __dealloc__(self): 
     print("Cython: __dealloc__ called") 
     free(<void*>self.data_ptr) 
     print("Cython: __dealloc__ done") 


def faster(length): 
    print("Cython: calling C++ function to do it") 
    cdef int *array = doit(length) 
    print("Cython: back from C++") 
    cdef np.ndarray ndarray 
    array_wrapper = ArrayWrapper() 
    array_wrapper.set_data(length, <void*> array) 
    print("Ctyhon: array wrapper set") 
    ndarray = np.array(array_wrapper, copy=False) 
    ndarray.base = <PyObject*> array_wrapper 
    Py_INCREF(array_wrapper) 
    print("Cython: all done - returning") 
    return ndarray 

setup.py

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Distutils import build_ext 
import numpy 

ext_modules = [Extension(
    "faster", 
    ["faster.pyx", "fast.cpp"], 
    language='c++', 
    extra_compile_args=["-std=c++11"], 
    extra_link_args=["-std=c++11"] 
)] 

setup(
    cmdclass = {'build_ext': build_ext}, 
    ext_modules = ext_modules, 
    include_dirs=[numpy.get_include()] 
) 

आप

python setup.py build_ext --inplace 

के साथ इस का निर्माण और अजगर 3.6 दुभाषिया चलाने के लिए, यदि आप में प्रवेश करता है, तो निम्नलिखित आप SEG मिलता था की कोशिश करता की एक जोड़ी के बाद गलती।

>>> from faster import faster 
>>> a = faster(1000000) 
Cython: calling C++ function to do it 
C++: doing it fast 
C++: did it really fast 
Cython: back from C++ 
Ctyhon: array wrapper set 
Cython: __array__ called 
Cython: __array__ done 
Cython: all done - returning 
>>> a = faster(1000000) 
Cython: calling C++ function to do it 
C++: doing it fast 
C++: did it really fast 
Cython: back from C++ 
Ctyhon: array wrapper set 
Cython: __array__ called 
Cython: __array__ done 
Cython: all done - returning 
Cython: __dealloc__ called 
Segmentation fault (core dumped) 

चीजों की युगल गौर करने योग्य

  • आप सरणी (fast.cpp में) वेक्टर के बजाय का उपयोग करते हैं यह एक आकर्षण की तरह काम करेगा!
  • यदि आप faster(1000000) पर कॉल करते हैं और परिणाम variable a के अलावा किसी अन्य चीज़ में डालते हैं तो यह काम करेगा।

आप दर्ज करते हैं faster(10) की तरह छोटी संख्या आप की तरह एक अधिक विस्तृत जानकारी प्राप्त करेंगे:

Cython: calling C++ function to do it 
C++: doing it fast 
C++: did it really fast 
Cython: back from C++ 
Ctyhon: array wrapper set 
Cython: __array__ called 
Cython: __array__ done 
Cython: all done - returning 
Cython: __dealloc__ called <--- Perhaps this happened too early or late? 
*** Error in 'python': double free or corruption (fasttop): 0x0000000001365570 *** 
======= Backtrace: ========= 
More info here .... 

यह वास्तव में puzzling है यही कारण है कि इस सरणियों के साथ ऐसा नहीं होता है? कोई बात नहीं क्या!

मैं वैक्टरों का बहुत उपयोग करता हूं और इन परिदृश्यों में उनका उपयोग करने में सक्षम होना पसंद करूंगा।

+0

@AndyG जब आपको लगता है यह हो रहा है करते हैं? जब 'काम' समारोह दूसरी बार बुलाया जाता है? क्या वह एक नया वेक्टर शुरू नहीं करेगा? या यह मूल रूप से पहले भरे वेक्टर का आकार बदल रहा है? यदि हां, तो क्यों? – NULL

+1

मुझे खेद से पहले वास्तव में आपके कोड को नहीं देखा था। आपके कोड में व्यवहार को अपरिभाषित किया गया है क्योंकि यह अस्थायी संदर्भ को वापस कर रहा है। 'Doit' के बाद वेक्टर गुंजाइश से बाहर चला जाता है। आपके पास सरणी के साथ एक ही समस्या होगी (मैं यहां 'std :: array' मान रहा हूं)। आपको एक सेगमेंटेशन गलती मिलती है, आप अपने आशीर्वादों को गिन सकते हैं, क्योंकि यह चुपचाप आपको कचरा दे सकता है। गतिशील रूप से आवंटित सरणी (सी-शैली सरणी) के साथ आपको यह समस्या नहीं होगी क्योंकि स्मृति को हटाया नहीं जा रहा है। मुझे लगता है कि आप मेमोरी को साइथन के पास ले जा सकते हैं, फिर? अन्यथा आपको मेमोरी लीक – AndyG

+0

@ एंडीजी देखेंगे, हाँ, जो समझ में आता है। जैसा कि मैंने उल्लेख किया है गतिशील सरणी (सी-शैली सरणी) ठीक काम करते हैं। क्या वैसे भी है कि मैं वापसी के लिए वेक्टर को सरणी में कॉपी करने से बच सकता हूं? – NULL

उत्तर

3

मुझे लगता है कि @ FlorianWeimer का जवाब एक सभ्य समाधान (एक vector आबंटित कर अपने सी ++ समारोह में पारित उस) प्रदान करता है लेकिन यह संभव टी होना चाहिए o doit से वेक्टर वापस करें और चालक कन्स्ट्रक्टर का उपयोग करके प्रतियों से बचें।

from libcpp.vector cimport vector 

cdef extern from "<utility>" namespace "std" nogil: 
    T move[T](T) # don't worry that this doesn't quite match the c++ signature 

cdef extern from "fast.h": 
    vector[int] doit(int length) 

# define ArrayWrapper as holding in a vector 
cdef class ArrayWrapper: 
    cdef vector[int] vec 
    cdef Py_ssize_t shape[1] 
    cdef Py_ssize_t strides[1] 

    # constructor and destructor are fairly unimportant now since 
    # vec will be destroyed automatically. 

    cdef set_data(self, vector[int]& data): 
     self.vec = move(data) 

    # now implement the buffer protocol for the class 
    # which makes it generally useful to anything that expects an array 
    def __getbuffer__(self, Py_buffer *buffer, int flags): 
     # relevant documentation http://cython.readthedocs.io/en/latest/src/userguide/buffer.html#a-matrix-class 
     cdef Py_ssize_t itemsize = sizeof(self.vec[0]) 

     self.shape[0] = self.vec.size() 
     self.strides[0] = sizeof(int) 
     buffer.buf = <char *>&(self.vec[0]) 
     buffer.format = 'i' 
     buffer.internal = NULL 
     buffer.itemsize = itemsize 
     buffer.len = self.v.size() * itemsize # product(shape) * itemsize 
     buffer.ndim = 1 
     buffer.obj = self 
     buffer.readonly = 0 
     buffer.shape = self.shape 
     buffer.strides = self.strides 
     buffer.suboffsets = NULL 

फिर आप के रूप में उपयोग करने के लिए सक्षम होना चाहिए:

cdef vector[int] array = doit(length) 
cdef ArrayWrapper w 
w.set_data(array) # "array" itself is invalid from here on 
numpy_array = np.asarray(w) 
+0

बहुत बढ़िया! बस कुछ सवाल। 1- मूल्यों के प्रकार के बारे में एक अच्छा दस्तावेज़ या स्रोत कहां है, बफर प्रारूप मिलता है, जैसे 'i', 'f', आदि? 2- तो numpy अब से स्मृति जारी करने का ख्याल रखता है? – NULL

+1

1. बफर प्रारूप बड़े पैमाने पर numpy सरणी मॉड्यूल https://docs.python.org/3/library/array.html द्वारा उपयोग किए गए मिलान से मेल खाते हैं। 2. हां - इस योजना में 'ऐरेप्रैपर' में स्मृति होती है और जब तक आवश्यक हो, तब तक उस जिंदा के लिए जिंदा संदर्भ रहता है, इसलिए आपको कुछ भी करने की आवश्यकता नहीं है। – DavidW

+0

मैंने आपके सरणी रैपर और मेरी कंपाइलर शिकायतों का उपयोग करने की कोशिश की जो 'चाल' को 'std' का सदस्य नहीं है। एक और सवाल, क्या होगा यदि हम वेक्टर [int] सरणी को सीधे numpy_array पर पास करते हैं? उदाहरण के लिए, numpy_array = np.asarray (सरणी)? इस तरह मैं पाइथन वर्ग गुणों में अपने सी ++ वैक्टर लौट रहा हूं। क्या यह ग़लत है? – GiorgosR

3

जब आप doit से वापस आते हैं, तो WhyNot ऑब्जेक्ट दायरे से बाहर हो जाता है, और सरणी तत्वों को हटा दिया जाता है।इसका मतलब है कि &WhyNot[0] अब मान्य सूचक नहीं है। आपको कहीं और WhyNot ऑब्जेक्ट को कहीं और, कॉलर द्वारा प्रदान की गई जगह पर स्टोर करने की आवश्यकता है।

एक तरह से यह करने के लिए तीन काम करता है, doit_allocate जो वेक्टर आवंटित करता है और यह करने के लिए एक सूचक देता है, doit से पहले के रूप में में doit विभाजित करने के लिए है (लेकिन एक तर्क जो पूर्व आबंटित वेक्टर , and doit_free` जो deallocates के लिए सूचक प्राप्त करता है साथ वेक्टर

कुछ इस तरह:।

vector<int> * 
doit_allocate() 
{ 
    return new vector<int>; 
} 

int * 
doit(vector<int> *WhyNot, int length) 
{ 
    // Something really heavy 
    cout << "C++: doing it fast " << endl; 

    // Heavy stuff - like reading a big file and preprocessing it 
    for(int i=0; i<length; ++i) 
     WhyNot->push_back(i); // heavy stuff 

    cout << "C++: did it really fast" << endl; 
    return WhyNot->front(); 
} 

void 
doit_free(vector<int> *WhyNot) 
{ 
    delete WhyNot; 
} 
+0

दाएं। यह अच्छा होगा अगर आप साइथन में ऐसा करने के बारे में विस्तार से बता सकते हैं उदा। कोड में क्या संशोधन? मैंने कभी साइथन में वैक्टरों से निपटा नहीं है। – NULL

+0

संपादन के लिए धन्यवाद। मेरा मतलब है कि साइथन में वेक्टर के साथ वेक्टर से कैसे निपटें। @ डेविडड प्रतिक्रिया इस पते को संबोधित करती है। – NULL

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