Cython并行OpenMP for Black Scholes与NumPy集成,串行代码10M选项3.5s,并行?

时间:2016-05-12 22:06:07

标签: python numpy openmp cython

以下是使用实际多线程在Cython中编写的期货期权的Black(Black Scholes减去股息)期权定价模型,但我无法运行它。 (现在固定,以后再看下面的答案)。我使用Python 3.5与Microsoft Visual Studio 2015编译器。以下是10M选项需要3.5秒的串行版本:Cython program is slower than plain Python (10M options 3.5s vs 3.25s Black Scholes) - what am I missing?

我尝试使用nogil使其并行,但在编译之后,我无法访问内部函数CyBlackP。这有几个问题(至少在Windows上)。 1)生成OpenMP代码时的Cython假设您超出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以删除已签名/未签名的错误,然后重新编译。 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,该函数以{{1}运行和OpenMP。然后将结果转换回NumPy数组并从nogil返回到Python]:

CyBlackP

下一个代码段另存为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.py使用:

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) 进行构建。

任何有关获取此功能的帮助都表示赞赏,不确定为什么我在编译后似乎无法找到它。我可以python setup.py build_ext --inplace --compiler=msvcimport CyBlackP但我无法使用实际函数来计算选项值。

如果你想测试这个Cython函数,这是一个现实的NumPy测试脚本:

from CyBlackP import *

1 个答案:

答案 0 :(得分:0)

好吧,我在Cython生成的CyBlackP.cp35-win_amd64.pyd文件中找出使用依赖性walker http://www.dependencywalker.com/的错误。它显示找不到2个DLL:msvcp140_app.dllvcomp140_app.dll只是x64版本的MSVC OpenMP,CRT 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)