2016-02-09 4 views
8

मैं वर्तमान में साइथन में मूल मैट्रिक्स वेक्टर गुणा को लागू करने की कोशिश कर रहा हूं (larger project to reduce computation के हिस्से के रूप में) और यह पता लगाना कि मेरा कोड Numpy.dot से 2x धीमी है।मैट्रिक्स वेक्टर गुणा के मेरे साइथन कार्यान्वयन में 2x मंदी का कारण क्या है?

मुझे आश्चर्य है कि क्या ऐसा कुछ है जो मुझे याद आ रहा है जिसके परिणामस्वरूप मंदी हुई है। मैं अनुकूलित साइथन कोड लिख रहा हूं, परिवर्तनीय प्रकार घोषित कर रहा हूं, संगत सरणी की आवश्यकता है, और कैश मिस से परहेज कर रहा हूं। मैंने साइथन को एक रैपर के रूप में रखने और देशी सी कोड को कॉल करने का प्रयास किया (नीचे देखें)।

मुझे आश्चर्य है: मेरे कार्यान्वयन को तेज करने के लिए मैं और क्या कर सकता हूं ताकि इस मूल ऑपरेशन के लिए जल्द से जल्द NumPy चल सके?


Cython कोड है कि मैं का उपयोग कर रहा है beow:

import numpy as np 
cimport numpy as np 
cimport cython 

DTYPE = np.float64; 
ctypedef np.float64_t DTYPE_T 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.nonecheck(False) 
def matrix_vector_multiplication(np.ndarray[DTYPE_T, ndim=2] A, np.ndarray[DTYPE_T, ndim=1] x): 

    cdef Py_ssize_t i, j 
    cdef Py_ssize_t N = A.shape[0] 
    cdef Py_ssize_t D = A.shape[1] 
    cdef np.ndarray[DTYPE_T, ndim=1] y = np.empty(N, dtype = DTYPE) 
    cdef DTYPE_T val 

    for i in range(N): 
     val = 0.0 
     for j in range(D): 
      val += A[i,j] * x[j] 
     y[i] = val 
    return y 

मैं इस फ़ाइल (seMatrixVectorExample.pyx) संकलन कर रहा हूँ निम्न स्क्रिप्ट का उपयोग कर:

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

ext_modules=[ Extension("seMatrixVectorExample", 
         ["seMatrixVectorExample.pyx"], 
         libraries=["m"], 
         extra_compile_args = ["-ffast-math"])] 

setup(
    name = "seMatrixVectorExample", 
    cmdclass = {"build_ext": build_ext}, 
    include_dirs = [np.get_include()], 
    ext_modules = ext_modules 
) 

और का उपयोग कर निम्नलिखित प्रदर्शन का आकलन करने के लिए परीक्षण स्क्रिप्ट:

import numpy as np 
from seMatrixVectorExample import matrix_vector_multiplication 
import time 

n_rows, n_cols = 1e6, 100 
np.random.seed(seed = 0) 

#initialize data matrix X and label vector Y 
A = np.random.random(size=(n_rows, n_cols)) 
np.require(A, requirements = ['C']) 

x = np.random.random(size=n_cols) 
x = np.require(x, requirements = ['C']) 

start_time = time.time() 
scores = matrix_vector_multiplication(A, x) 
print "cython runtime = %1.5f seconds" % (time.time() - start_time) 

start_time = time.time() 
py_scores = np.exp(A.dot(x)) 
print "numpy runtime = %1.5f seconds" % (time.time() - start_time) 

n_rows = 10e6 और n_cols = 100 के साथ एक परीक्षण मैट्रिक्स के लिए मैं मिलता है:

cython runtime = 0.08852 seconds 
numpy runtime = 0.04372 seconds 

संपादित करें: ऐसा नहीं है कि मंदी भी बनी रहती है जब मैं देशी सी कोड में आव्यूह गुणन को लागू उल्लेख के लायक है, और केवल उपयोग एक रैपर के रूप में साइथन।

void c_matrix_vector_multiplication(double* y, double* A, double* x, int N, int D) { 

    int i, j; 
    int index = 0; 
    double val; 

    for (i = 0; i < N; i++) { 
     val = 0.0; 
     for (j = 0; j < D; j++) { 
      val = val + A[index] * x[j]; 
      index++; 
      } 
     y[i] = val; 
     } 
    return; 
} 

और यहाँ Cython आवरण, जो सिर्फ y, A और x के पहले तत्व के लिए सूचक भेजता है। :

import cython 
import numpy as np 
cimport numpy as np 

DTYPE = np.float64; 
ctypedef np.float64_t DTYPE_T 

# declare the interface to the C code 
cdef extern void c_multiply (double* y, double* A, double* x, int N, int D) 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.nonecheck(False) 
def multiply(np.ndarray[DTYPE_T, ndim=2, mode="c"] A, np.ndarray[DTYPE_T, ndim=1, mode="c"] x): 

    cdef int N = A.shape[0] 
    cdef int D = A.shape[1] 
    cdef np.ndarray[DTYPE_T, ndim=1, mode = "c"] y = np.empty(N, dtype = DTYPE) 

    c_multiply (&y[0], &A[0,0], &x[0], N, D) 

    return y 
+0

[यह] (http://stackoverflow.com/questions/10442365/why-is-matrix-multiplication-faster-with-numpy-than-with-ctypes-in-python) प्रश्न/उत्तर संबंधित उत्तर के शीर्ष कारणों से संबंधित विभिन्न कारणों से संबंधित है। इसकी जांच - पड़ताल करें। – russianfool

+0

@russianfool धन्यवाद! मैंने वास्तव में उस प्रश्न के उत्तर के माध्यम से पढ़ा था, लेकिन दिए गए कारण इस समस्या से प्रासंगिक नहीं हैं क्योंकि मै मैट्रिक्स-मैट्रिक्स गुणा के बजाय मैट्रिक्स-वेक्टर गुणा से निपट रहा हूं। मैं इसे अपने प्रश्न में स्पष्ट कर दूंगा। –

+2

कुछ हद तक मुझसे संबंधित लगता है; अर्थात्, बीएलएएस/अनियंत्रित लूप के बारे में बिट्स पढ़ें। आप एक मैट्रिक्स-वेक्टर गुणा कार्यान्वयन [यहां] (http://www.netlib.org/clapack/cblas/cgemv.c) पा सकते हैं, और यह निश्चित रूप से ऐसा लगता है कि आपके पास उस डेटा के आधार पर विभिन्न अनुकूलित संस्करण हैं जो आप हैं गुजर रहा है। एक तरफ ध्यान दें, मैं distutils से बहुत परिचित नहीं हूँ ... क्या आप -O2 को अतिरिक्त_कंपाइल_र्ग में से एक के रूप में पास कर सकते हैं? यदि यह हुड के तहत -ओ 2 के साथ संकलित नहीं है, तो प्रदर्शन की तुलना करने के लिए यह समझ में नहीं आता है। – russianfool

उत्तर

3

ठीक है अंत में रनटाइम्स प्राप्त करने में कामयाब रहे जो न्यूम्पी से बेहतर हैं!

यहां क्या है (मुझे लगता है) अंतर का कारण बनता है: NumPy बीएलएएस कार्यों को बुला रहा है, जो सी के बजाय फोरट्रान में कोडित होते हैं, जिसके परिणामस्वरूप गति अंतर होता है।

मुझे लगता है कि यह ध्यान रखना महत्वपूर्ण है, क्योंकि मैं पहले इस धारणा के तहत था कि बीएलएएस कार्यों को सी में कोडित किया गया था और यह नहीं देख सका कि वे दूसरे देशी सी कार्यान्वयन से काफी तेजी से क्यों चलेंगे ।

import cython 
import numpy as np 
cimport numpy as np 
cimport scipy.linalg.cython_blas as blas 

DTYPE = np.float64 
ctypedef np.float64_t DTYPE_T 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.nonecheck(False) 

def blas_multiply(np.ndarray[DTYPE_T, ndim=2, mode="fortran"] A, np.ndarray[DTYPE_T, ndim=1, mode="fortran"] x): 
    #calls dgemv from BLAS which computes y = alpha * trans(A) + beta * y 
    #see: http://www.nag.com/numeric/fl/nagdoc_fl22/xhtml/F06/f06paf.xml 

    cdef int N = A.shape[0] 
    cdef int D = A.shape[1] 
    cdef int lda = N 
    cdef int incx = 1 #increments of x 
    cdef int incy = 1 #increments of y 
    cdef double alpha = 1.0 
    cdef double beta = 0.0 
    cdef np.ndarray[DTYPE_T, ndim=1, mode = "fortran"] y = np.empty(N, dtype = DTYPE) 

    blas.dgemv("N", &N, &D, &alpha, &A[0,0], &lda, &x[0], &incx, &beta, &y[0], &incy) 

    return y 

:

या तो मामले में, मैं अब प्रदर्शन Cython + SciPy Cython BLAS समारोह संकेत का उपयोग करके से scipy.linalg.cython_blas.


पूर्णता के लिए दोहराने कर सकते हैं, यहाँ नया Cython कोड blas_multiply.pyx है यहां कोड है जिसका उपयोग मैं बनाने के लिए करता हूं:

!/usr/bin/env python 

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

import numpy 
import scipy 

ext_modules=[ Extension("blas_multiply", 
         sources=["blas_multiply.pyx"], 
         include_dirs=[numpy.get_include(), scipy.get_include()], 
         libraries=["m"], 
         extra_compile_args = ["-ffast-math"])] 

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

और यहाँ परीक्षण कोड है (ध्यान दें कि BLAS कार्य करने के लिए पारित कर दिया सरणियों अब F_CONTIGUOUS हैं)

import numpy as np 
from blas_multiply import blas_multiply 
import time 

#np.__config__.show() 
n_rows, n_cols = 1e6, 100 
np.random.seed(seed = 0) 

#initialize data matrix X and label vector Y 
X = np.random.random(size=(n_rows, n_cols)) 
Y = np.random.randint(low=0, high=2, size=(n_rows, 1)) 
Y[Y==0] = -1 
Z = X*Y 
Z.flags 
Z = np.require(Z, requirements = ['F']) 

rho_test = np.random.randint(low=-10, high=10, size= n_cols) 
set_to_zero = np.random.choice(range(0, n_cols), size =(np.floor(n_cols/2), 1), replace=False) 
rho_test[set_to_zero] = 0.0 
rho_test = np.require(rho_test, dtype=Z.dtype, requirements = ['F']) 

start_time = time.time() 
scores = blas_multiply(Z, rho_test) 
print "Cython runtime = %1.5f seconds" % (time.time() - start_time) 


Z = np.require(Z, requirements = ['C']) 
rho_test = np.require(rho_test, requirements = ['C']) 
start_time = time.time() 
py_scores = np.exp(Z.dot(rho_test)) 
print "Python runtime = %1.5f seconds" % (time.time() - start_time) 

मेरी मशीन पर इस परीक्षण से परिणाम है:

Cython runtime = 0.04556 seconds 
Python runtime = 0.05110 seconds 
+0

मुझे पूछना है ... क्या यह वास्तव में 10% प्रदर्शन टक्कर के लिए सभी प्रयासों के लायक था? संख्यात्मक/पायथन ओवरहेड की मात्रा सरणी आयामों के संबंध में मोटे तौर पर स्थिर रहेगी, इसलिए जब आप इसे बड़े और बड़े डेटासेट पर लागू करते हैं तो मैं तेजी से कम रिटर्न देखने की अपेक्षा करता हूं। साइथन * से बीएलए को कॉल करना * समझ में आता है यदि आप बहुत बड़ी संख्या में छोटी मैट्रिक्स के लिए मैट्रिक्स उत्पादों की गणना कर रहे हैं (लेकिन उस स्थिति में भी आप 'np.dot' या' np.matmul' 'का उपयोग करके बहुत अच्छी तरह से कर सकते हैं वेक्टरेशन ...)। एक बड़े मैट्रिक्स उत्पाद के लिए यह लगभग कोई फर्क नहीं पड़ता है। –

+0

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

+0

बीएलएएस के बारे में कुछ भी जादू नहीं है, लेकिन यह कुशल फोरट्रान प्रोग्रामर के समूह के प्रयासों का प्रतिनिधित्व करता है जो प्रोसेसर के कुछ विशिष्ट मॉडल से प्रदर्शन की हर आखिरी बूंद को निचोड़ने के लिए इन दिनचर्या के अनुकूलित संस्करणों को हाथ से तैयार करने के लिए तैयार हैं। मैं ईमानदारी से थोड़ा आश्चर्यचकित हूं कि आप बेवकूफ मैट्रिक्स-वेक्टर गुणा के साथ दो के कारक के भीतर भी पहुंच सकते हैं। एक अनुकूलित बीएलएएस दिनचर्या के लिए एक कॉल शायद सबसे अच्छा है जो आप घने मैट्रिक्स-वेक्टर गुणा के लिए कर सकते हैं, इसे जीपीयू पर कर सकते हैं ... –

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