2016-05-11 9 views
3

Okay, hier ist mein erstes Cython-Programm, der Preiscode für europäische Optionen auf Futures (Black Scholes ohne Dividende). Es läuft in 3.5s auf 10M Optionen, gegen den Code, den ich unten mit geraden numpy Python 3.25s postete. Kann jemand darauf hinweisen, warum mein Cython-Code langsamer ist - etwa weil ich eine Schleife verwendet habe, anstatt den Aufruf zu vektorisieren (nicht sicher in C, wie das zu tun ist, scheint der erzeugte Cython-Code ihn zu vektorisieren). Kann ich nogil und openmp um diese Schleife verwenden, obwohl die Variablen von numpy Arrays übergeben werden? Die einfachen Beispiele, die in Cythons Beispielen gepostet werden, werden nicht korrekt mit cython.parallelprange auf der Schleife http://docs.cython.org/src/userguide/parallelism.html#module-cython.parallel kompiliert. Feedback sehr geschätzt, Entschuldigungen für eine etwas offene Frage - andere können diesen Code hier frei als Ausgangspunkt verwenden, da es bereits schneller arbeitet als andere Arbeit profiliert online, die ich gesehen habe, in C und Python. Hier ist sie:Cython-Programm ist langsamer als normal Python (10M Optionen 3.5 vs 3.25s Black Scholes) - was vermisse ich?

Speichern als CyBlack.pyx Datei zu kompilieren (beachten Sie alle Eingänge float64 außer Black_callput sind die int64, 1 für einen Anruf ist, -1 für eine Put). Nach dem Kompilieren from CyBlack.CyBlack import CyBlack:

from numpy cimport ndarray 
cimport numpy as np 
cimport cython 

cdef extern from "math.h": 
    double exp(double) 
    double sqrt(double) 
    double log(double) 
    double erf(double) 

cdef double std_norm_cdf(double x): 
    return 0.5*(1+erf(x/sqrt(2.0))) 

@cython.boundscheck(False) 
cpdef CyBlack(ndarray[np.float64_t, ndim=1] BlackPnL, ndarray[np.float64_t, ndim=1] Black_S, ndarray[np.float64_t, ndim=1] Black_Texpiry, ndarray[np.float64_t, ndim=1] Black_strike, ndarray [np.float64_t, ndim=1] Black_volatility, ndarray[np.float64_t, ndim=1] Black_IR, ndarray[np.int64_t, ndim=1] Black_callput): 

    cdef Py_ssize_t i 
    cdef Py_ssize_t N = BlackPnL.shape[0] 
    cdef double d1, d2 


    for i in range(N): 
     d1 = ((log(Black_S[i]/Black_strike[i]) + Black_Texpiry[i] * Black_volatility[i] **2/2))/(Black_volatility[i] * sqrt(Black_Texpiry[i])) 
     d2 = d1 - Black_volatility[i] * sqrt(Black_Texpiry[i]) 
     BlackPnL[i] = exp(-Black_IR[i] * Black_Texpiry[i]) * (Black_callput[i] * Black_S[i] * std_norm_cdf(Black_callput[i] * d1) - Black_callput[i] * Black_strike[i] * std_norm_cdf(Black_callput[i] * d2)) 

    return BlackPnL 

Hier ist die setup.py, damit andere diese Typisierung aufbauen können: python setup.py build_ext --inplace mit VS2015 gebaut für Python 3.5 64-Bit-Windows-.

from setuptools import setup 
from setuptools import Extension 
from Cython.Distutils import build_ext 
import numpy as np 

ext_modules = [Extension("CyBlack",sources=["CyBlack.pyx"], 
      extra_compile_args=['/Ox', '/openmp', '/favor:INTEL64'], 
      language='c++')] 

setup(
    name= 'Generic model class', 
    cmdclass = {'build_ext': build_ext}, 
    include_dirs = [np.get_include()], 
    ext_modules = ext_modules) 

Okay, und hier ist mein sehr schnell numpy Python nur Code:

import numpy as np 
from scipy.stats import norm 

d1=((np.log(Black_S/Black_strike) + Black_Texpiry * Black_volatility **2/2))/(Black_volatility * np.sqrt(Black_Texpiry)) 
d2=d1 - Black_volatility * np.sqrt(Black_Texpiry) 
BlackPnL = np.exp(-Black_IR * Black_Texpiry) * (Black_callput * Black_S * norm.cdf(Black_callput * d1) - Black_callput * Black_strike * norm.cdf(Black_callput * d2)) 
+6

Sie sollten in der Regel nicht Cython erwarten eine große Beschleunigung bieten über vektorisierte Nummernoperationen. Die anzahlmäßigen Operationen werden bereits in C ausgeführt. – BrenBarn

+4

BrenBarn hat Recht - es sind die * nicht-vektorisierten * Operationen, die Sie am meisten von einem Absturz auf C profitieren. Eine Sache, die Sie * jedoch tun können, um zu sehen, ob es Optimierungen gibt noch nicht erfasst ist, cython mit '-a' auszuführen und den resultierenden HTML-Code in einem Browser zu betrachten. Ein schneller Skim lässt es zum Beispiel so aussehen, als würde es "pow" heißen anstatt quadrieren. – DSM

+0

Macht Sinn, ich habe 'pow' in' ** 'und' sqrt' in '** 0.5' geändert, ich dachte, ich könnte etwas mit' no gil' und 'openmp' machen, um zumindest Multi Threading zu bekommen . Dies ist nur das einfachste Finanzbeispiel, das ein guter Baustein für alle anderen analytischen Optionspreisbibliotheken ist, daher denke ich, dass andere davon profitieren werden, diesen Beitrag zu lesen. – Matt

Antwort

2

Ich habe die folgenden Zeilen vor Ihren Funktionen in Ihrem cython Code und ich habe schneller ein Ergebnis aus Cython als Python 2.7

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 

Meine Ergebnisse für 10M Punkte

%timeit PyBlack(BlackPnL, Black_S, Black_Texpiry, Black_strike, Black_volatility, Black_IR, Black_callput) 
1 loops, best of 3: 3.49 s per loop 

und

%timeit CyBlack(BlackPnL, Black_S, Black_Texpiry, Black_strike, Black_volatility, Black_IR, Black_callput) 
1 loops, best of 3: 2.12 s per loop 

EDIT

CyBlack.pyx

from numpy cimport ndarray 
cimport numpy as np 
cimport cython 

cdef extern from "math.h": 
    double exp(double) 
    double sqrt(double) 
    double log(double) 
    double fabs(double) 

cdef double a1 = 0.254829592 
cdef double a2 = -0.284496736 
cdef double a3 = 1.421413741 
cdef double a4 = -1.453152027 
cdef double a5 = 1.061405429 
cdef double p = 0.3275911 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 
cdef inline double erf(double x): 
    cdef int sign = 1 
    if (x < 0): 
     sign = -1 
    x = fabs(x) 

    cdef double t = 1.0/(1.0 + p*x) 
    cdef double y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*exp(-x*x) 

    return sign*y 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 
cdef double std_norm_cdf(double x): 
    return 0.5*(1+erf(x/sqrt(2.0))) 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 
cpdef CyBlack(ndarray[np.float64_t, ndim=1] BlackPnL, ndarray[np.float64_t, ndim=1] Black_S, ndarray[np.float64_t, ndim=1] Black_Texpiry, ndarray[np.float64_t, ndim=1] Black_strike, ndarray [np.float64_t, ndim=1] Black_volatility, ndarray[np.float64_t, ndim=1] Black_IR, ndarray[np.int64_t, ndim=1] Black_callput): 

    cdef Py_ssize_t i 
    cdef Py_ssize_t N = BlackPnL.shape[0] 
    cdef double d1, d2 


    for i in range(N): 
     d1 = ((log(Black_S[i]/Black_strike[i]) + Black_Texpiry[i] * Black_volatility[i] **2/2))/(Black_volatility[i] * sqrt(Black_Texpiry[i])) 
     d2 = d1 - Black_volatility[i] * sqrt(Black_Texpiry[i]) 
     BlackPnL[i] = exp(-Black_IR[i] * Black_Texpiry[i]) * (Black_callput[i] * Black_S[i] * std_norm_cdf(Black_callput[i] * d1) - Black_callput[i] * Black_strike[i] * std_norm_cdf(Black_callput[i] * d2)) 

    return BlackPnL 

setup.py

try: 
    from setuptools import setup 
    from setuptools import Extension 
except ImportError: 
    from distutils.core import setup 
    from distutils.extension import Extension 

from Cython.Distutils import build_ext 
import numpy as np 

ext_modules = [Extension("CyBlack",["CyBlack.pyx"])] 

setup(
    name= 'Generic model class', 
    cmdclass = {'build_ext': build_ext}, 
    include_dirs = [np.get_include()], 
    ext_modules = ext_modules) 
+0

Ich hatte das früher versucht und aus irgendeinem Grund auf Python 3.5 verbessert es die Leistung überhaupt nicht. Ich habe keine Ahnung warum ... – Matt

+0

Haben Sie die Compiler-Optionen zufällig geändert? – Matt

+0

Ich habe den Code hinzugefügt, den ich in meiner Antwort verwendet habe. Ich habe deine '' 'extra_compile_args = ['/ EHsc', '/ openmp', '/ favor: INTEL64']' '' nicht benutzt und ich musste meine eigene '' 'erf''' Funktion schreiben. – sebacastroh