Cython parallel OpenMP for Black Scholes with NumPy integrated, serial code 10M options 3.5s, parallel?

Cython parallel OpenMP for Black Scholes with NumPy integrated, serial code 10M options 3.5s, parallel?

这是 Black(Black Scholes 减去股息)期权定价模型,用于用 Cython 编写的具有实际多线程的期货期权,但我不能 运行 它。 (现已修复,稍后请参阅下面的 POST 以获取答案)。我正在使用 Python 3.5 和 Microsoft Visual Studio 2015 编译器。这是 10M 选项需要 3.5s 的串行版本:

我试图通过使用 nogil 使其并行,但在编译之后,我无法访问内部函数 CyBlackP。这有几个问题(至少在 Windows 上)。 1) Cython 在生成 OpenMP 代码时假定您已超出 v2.0,但 Microsoft Visual Studio 2015 停留在需要签名迭代器的旧版本上。我的解决方法是在第一次尝试构建代码后,它会出错,然后在 Microsoft Visual Studio 2015 中打开输出 CyBlackP.cpp 文件,搜索 size_t __pyx_t_2(第 1430 行),然后将其更改为 ssize_t __pyx_t_2,并将下一行从 size_t __pyx_t_3 更改为 ssize_t __pyx_t_3 以消除 signed/unsigned 错误,然后再次编译。 2) 你不能直接从 NumPy 数组进入函数,因为 nogil 只适用于纯 C/C++ 函数,所以我有几个辅助函数将 NumPy 数组输入转换成 C++ vector 格式,将它们传递给 C++ 函数,然后将返回的 vector 转换回 NumPy 数组。我在这里发布并行代码供其他人使用,我相信有人可以弄清楚为什么我无法从 Python 访问并行函数 - 非并行版本是这样访问的 from CyBlackP.CyBlackP import CyBlackP

此处提供了代码以及有关如何构建的步骤。第一个文件另存为 CyBlackP.pyx [注意这里暴露给 Python 的函数是 CyBlackP,它通过辅助函数将 NumPy 输入数组转换为 C 向量,然后将 C 向量传递给 C 函数 CyBlackParallel,运行s nogil 和 OpenMP。然后将结果转换回 NumPy 数组并从 CyBlackP 返回到 Python]:

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

下一段代码保存为setup.pyCython使用:

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)

然后在命令提示符下键入:python setup.py build_ext --inplace --compiler=msvc 进行构建。

感谢任何有关访问此功能的帮助,不知道为什么我在编译后似乎无法找到它。我可以 import CyBlackPfrom CyBlackP import * 但我无法使用实际函数来计算期权值。

如果你想测试这个 Cython 函数,这里有一个真实的 NumPy 测试脚本:

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)

好的,我发现在 Cython 生成的 CyBlackP.cp35-win_amd64.pyd 文件上使用 dependency walker http://www.dependencywalker.com/ 出了什么问题。它显示未找到 2 个 DLL:msvcp140_app.dllvcomp140_app.dll 它们只是 MSVC OpenMP 和 CRT 的 x64 版本 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\ Microsoft.VC140.OpenMP\vcomp140.dllC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\Microsoft.VC14 0.CRT\msvcp140.dll 重命名为插入 _app , 并复制到 \CyBlackP\ 项目目录。我还像这样更新了我的 setup.py,它摆脱了烦人的导入语句(现在只是 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)