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.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=msvc
进行构建。
感谢任何有关访问此功能的帮助,不知道为什么我在编译后似乎无法找到它。我可以 import CyBlackP
或 from 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.dll
和 vcomp140_app.dll
它们只是 MSVC OpenMP 和 CRT 的 x64 版本 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\
Microsoft.VC140.OpenMP\vcomp140.dll
和 C:\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)
这是 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.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=msvc
进行构建。
感谢任何有关访问此功能的帮助,不知道为什么我在编译后似乎无法找到它。我可以 import CyBlackP
或 from 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.dll
和 vcomp140_app.dll
它们只是 MSVC OpenMP 和 CRT 的 x64 版本 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\
Microsoft.VC140.OpenMP\vcomp140.dll
和 C:\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)