2016-05-12 10 views
1

Hier ist das schwarze Preismodell (Black Scholes abzüglich der Dividende) für Optionen auf Futures, geschrieben in Cython mit tatsächlichem Multi-Threading, aber ich kann es nicht ausführen. (JETZT FESTGELEGT, SIEHE SPÄTER NACH UNTEN ZUR ANTWORT). Ich verwende Python 3.5 mit Microsoft Visual Studio 2015 Compiler. Hier ist die Serienversion, die 3.5s für 10M Optionen nimmt: Cython program is slower than plain Python (10M options 3.5s vs 3.25s Black Scholes) - what am I missing?Cython parallel OpenMP für Black Scholes mit NumPy integriertem, seriellem Code 10M Optionen 3.5s, parallel?

ich versuchte, diese parallel zu machen, indem nogil verwenden, aber nach dem Kompilieren, kann ich nicht Zugriff auf die interne Funktion CyBlackP. Es gibt mehrere Probleme (zumindest unter Windows). 1) Cython geht bei der Generierung des OpenMP-Codes davon aus, dass Sie über V2.0 hinausgehen, aber Microsoft Visual Studio 2015 hängt an der alten Version fest, für die signierte Iteratoren erforderlich sind. Die Problemumgehung, die ich habe, ist nach dem ersten Versuch, den Code zu erstellen, wird es aus, öffnen Sie dann die Ausgabe CyBlackP.cpp Datei in Microsoft Visual Studio 2015, suchen Sie nach size_t __pyx_t_2 (Zeile 1430), ändern Sie es dann in ssize_t __pyx_t_2, und ändern Sie die nächste Zeile aus size_t __pyx_t_3 zu ssize_t __pyx_t_3, um signierte/unsignierte Fehler zu entfernen und erneut zu kompilieren. 2) Sie können nicht direkt von NumPy-Arrays in die Funktion gehen, da nogil nur auf reinen C/C++ - Funktionen funktioniert, also habe ich mehrere Hilfsfunktionen, um die NumPy-Array-Eingaben in C++ vector-Format zu konvertieren, dann an eine C++ - Funktion übergeben wandle die zurückgegebene vector zurück in ein NumPy-Array um. Ich poste den parallelen Code hier für andere, und ich bin mir sicher, dass jemand da draußen herausfinden kann, warum ich nicht auf die parallele Funktion von Python zugreifen kann - auf die nicht-parallele Version wurde so zugegriffen from CyBlackP.CyBlackP import CyBlackP.

Code ist hier mit Schritten zum Erstellen. Erste Datei speichern unter CyBlackP.pyx [beachten Sie die exposed-Funktion zu Python hier ist CyBlackP, die die NumPy Eingabe Arrays in C-Vektoren durch die Hilfsfunktionen konvertiert, dann übergibt die C-Vektoren CyBlackParallel, die mit nogil und OpenMP läuft. Die Ergebnisse werden dann wieder zu einem NumPy Array umgewandelt und kehrten von CyBlackP zu Python zurück]:

import numpy as np 
cimport cython 
from cython.parallel cimport prange 
from libcpp.vector cimport vector 

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

cdef double std_norm_cdf(double x) nogil: 

    return 0.5*(1+erf(x/sqrt(2.0))) 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 
cdef CyBlackParallel(vector[double] Black_PnL, vector[double] Black_S, vector[double] Black_Texpiry, vector[double] Black_strike, vector[double] Black_volatility, vector[double] Black_IR, vector[int] Black_callput): 
    cdef int i 
    N = Black_PnL.size() 

    cdef double d1, d2 

    for i in prange(N, nogil=True, num_threads=4, schedule='static'): 
     d1 = ((log(Black_S[i]/Black_strike[i]) + Black_Texpiry[i] * (Black_volatility[i] * Black_volatility[i])/2))/(Black_volatility[i] * sqrt(Black_Texpiry[i])) 
     d2 = d1 - Black_volatility[i] * sqrt(Black_Texpiry[i]) 
     Black_PnL[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 Black_PnL 

cdef vector[double] arrayToVector(np.ndarray[np.float64_t,ndim=1] array): 
    cdef long size = array.size 
    cdef vector[double] vec 
    cdef long i 
    for i in range(size): 
     vec.push_back(array[i]) 

    return vec 

cdef vector[int] INTarrayToVector(np.ndarray[np.int64_t,ndim=1] array): 
    cdef long size = array.size 
    cdef vector[int] vec 
    cdef long i 
    for i in range(size): 
     vec.push_back(array[i]) 

    return vec 

cdef np.ndarray[np.float64_t, ndim=1] vectorToArray(vector[double] vec): 
    cdef np.ndarray[np.float64_t, ndim=1] arr = np.zeros(vec.size()) 
    cdef long i 
    for i in range(vec.size()): 
     arr[i] = vec[i] 

    return arr 

@cython.boundscheck(False) 
@cython.wraparound(False) 
@cython.cdivision(True) 
cpdef CyBlackP(ndarray[np.float64_t, ndim=1] PnL, ndarray[np.float64_t, ndim=1] S0, ndarray[np.float64_t, ndim=1] Texpiry, ndarray[np.float64_t, ndim=1] strike, ndarray [np.float64_t, ndim=1] volatility, ndarray[np.float64_t, ndim=1] IR, ndarray[np.int64_t, ndim=1] callput): 
    cdef vector[double] Black_PnL, Black_S, Black_Texpiry, Black_strike, Black_volatility, Black_IR 
    cdef ndarray[np.float64_t, ndim=1] Results 
    cdef vector[int] Black_callput 
    Black_PnL = arrayToVector(PnL) 
    Black_S = arrayToVector(S0) 
    Black_Texpiry = arrayToVector(Texpiry) 
    Black_strike = arrayToVector(strike) 
    Black_volatility = arrayToVector(volatility) 
    Black_IR = arrayToVector(IR) 
    Black_callput = INTarrayToVector(callput) 
    Black_PnL = CyBlackParallel (Black_PnL, Black_S, Black_Texpiry, Black_strike, Black_volatility, Black_IR, Black_callput) 
    Results = vectorToArray(Black_PnL) 

    return Results 

nächstes Codestück Speicher als setup.py zur Verwendung durch Cython:

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("CyBlackP",sources=["CyBlackP.pyx"], 
       extra_compile_args=['/Ot', '/openmp', '/favor:INTEL64', '/EHsc', '/GA'], 
       language='c++')] 

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

dann aus einer Eingabeaufforderung Typ: python setup.py build_ext --inplace --compiler=msvc zu bauen.

Jede Hilfe beim Zugriff auf diese Funktion ist willkommen, ich bin mir nicht sicher, warum ich sie nach dem Kompilieren nicht finden kann. Ich kann import CyBlackP oder from CyBlackP import * aber ich kann nicht auf die eigentliche Funktion, um die Option Werte zu berechnen.

ist hier ein realistisches NumPy Testskript zu verwenden, wenn Sie diese Cython Funktion testen wollen:

BlackPnL = np.zeros(10000000) 
Black_S=np.random.randint(200, 10000, 10000000)*0.01 
Black_Texpiry=np.random.randint(1,500,10000000)*0.01 
Black_strike=np.random.randint(1,100,10000000)*0.1 
Black_volatility=np.random.rand(10000000)*1.2 
Black_IR=np.random.rand(10000000)*0.1 
Black_callput=np.sign(np.random.randn(10000000)) 
Black_callput=Black_callput.astype(np.int64) 
+2

* "Sie können nicht direkt von NumPy Arrays in die Funktion gehen, wie' nogil' funktioniert nur auf reine C/C++ - Funktionen "* - Sie können existierende numpy Arrays entweder als typisierte memoryviews (zB' double [:] array'') oder mit der alten Syntax '' np.darray [...] array'' übergeben, ohne die GIL zu erwerben, Sie können sie nur nicht instanziieren oder auf ihre Python-Methoden innerhalb eines 'nogil'-Blocks zugreifen. –

+0

Obwohl die Funktion die NumPy-Arrays in C++ - Vektoren konvertiert, bevor die C++ - Funktion nur mit 'nogil' verarbeitet wird, heißt es, dass die Funktion cpdef überhaupt nicht Python zur Verfügung gestellt wird? Der Compiler kompiliert keinen 'nogil'-Block, wenn er ein Python-Objekt berührt, während dies kompiliert wird. – Matt

+1

Ein NumPy-Array ist im Wesentlichen ein Python-Wrapper um einen Speicherpuffer, der direkt mit C/C++ - Funktionen in Verbindung gebracht werden kann - es ist keine Konvertierung erforderlich. Werfen Sie einen Blick auf die [Dokumentation hier] (http://docs.cython.org/src/userguide/memoryviews.html). Ich bin mir nicht sicher, ob dies direkt mit dem beschriebenen Problem zusammenhängt, aber die Beseitigung all dieser unnötigen Hilfsfunktionen würde Ihren Code erheblich vereinfachen und das Debugging vereinfachen. –

Antwort

0

Okay, ich was falsch war mit Dependency Walker http://www.dependencywalker.com/ auf der CyBlackP.cp35-win_amd64.pyd erzeugte Datei von Cython herausgefunden. Es wurde gezeigt, dass 2 DLLs nicht gefunden wurden: msvcp140_app.dll und vcomp140_app.dll, die nur die x64-Versionen von MSVC OpenMP und CRT C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\ Microsoft.VC140.OpenMP\vcomp140.dll und C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\Microsoft.VC14 0.CRT\msvcp140.dll sind, die mit _app eingefügt und in das \CyBlackP\ Projektverzeichnis kopiert wurden.Ich meine auch setup.py wie diese aktualisiert, die von den lästigen Import-Anweisung entledigt (jetzt nur from CyBlackP import CyBlackP):

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 
import os 

module = 'CyBlackP' 

ext_modules = [Extension(module, sources=[module + ".pyx"], 
       extra_compile_args=['/Ot', '/favor:INTEL64', '/EHsc', '/GA', '/openmp'], 
       language='c++')] 

setup(
    name = module, 
    cmdclass = {'build_ext': build_ext}, 
    include_dirs = [np.get_include(), os.path.join(np.get_include(), 'numpy')], 
    ext_modules = ext_modules) 
+0

'% timeit CyBlackP (BlackPnL, Black_S, Black_Texpiry, Black_strike, Black_volatility, Black_IR, Black_callput) 1 Schleife, best of 3: 2.88 s pro Schleife' - im Taskmanager dies überprüft geht zu 4 Kernen wie im Code angegeben. – Matt

+0

Ok, das Problem war 'name = 'Generische Modellklasse', weil das der Name des zu importierenden Moduls ist. Fehlende DLLs sollten die Importlogik nicht ändern, sie sollten zu Ausnahmen (oder Abstürzen) führen, aber nichts mit dem Import zu tun haben. – dashesy

+0

Nicht so sicher, wie dieser Name mit der seriellen Version des Codes funktionierte ... und Sie mussten es auch nicht nennen. – Matt