链接Cython包装的C函数,以对抗来自NumPy的BLAS
我想在Cython扩展中使用一些在.C文件中定义的C函数,这些文件使用BLAS子例程,例如 c文件链接Cython包装的C函数,以对抗来自NumPy的BLAS,numpy,cython,blas,Numpy,Cython,Blas,我想在Cython扩展中使用一些在.C文件中定义的C函数,这些文件使用BLAS子例程,例如 c文件 double ddot(int *N, double *DX, int *INCX, double *DY, int *INCY); double call_ddot(double* a, double* b, int n){ int one = 1; return ddot(&n, a, &one, b, &one); } (假设函数不仅仅调用一个BL
double ddot(int *N, double *DX, int *INCX, double *DY, int *INCY);
double call_ddot(double* a, double* b, int n){
int one = 1;
return ddot(&n, a, &one, b, &one);
}
(假设函数不仅仅调用一个BLAS子例程)
pyfile.pyx
cimport numpy as np
import numpy as np
cdef extern from "cfile.c":
double call_ddot(double* a, double* b, int n)
def pyfun(np.ndarray[double, ndim=1] a):
return call_ddot(&a[0], &a[0], <int> a.shape[0])
我希望此软件包链接到已安装的NumPy或SciPy正在使用的同一BLAS库,并希望它可以在不同操作系统下使用NumPy或SciPy作为依赖项从PIP安装,而无需任何其他BLAS相关依赖项
对于setup.py
是否有任何黑客可以让我完成这项工作,并且可以与任何BLAS实现一起工作
更新:
使用MKL,我可以通过修改扩展
对象来指向libmkl\u rt
,如果安装了MKL,可以从numpy中提取,例如:
Extension(“wrapped_cfun.cython_part”,sources=[“pyfile.pyx”],include_dirs=[numpy.get_include()],extra_link_args=[“-L{path to python's libdir}”,“-L:libmkl_rt.{so,dll,dylib}])
但是,同样的技巧不适用于OpenBLAS(例如,-l:libopenblasp-r0.2.20.so
)。指向libblas.{so,dll,dylib}
如果该文件是指向libopenblas的链接,则该文件将不起作用,但如果它是指向libmkl\u rt的链接,则可以正常工作
更新2:
OpenBLAS似乎在其C函数的末尾加了下划线,例如,不是
ddot
,而是ddot
。如果我将.c文件中的ddot
更改为ddot\uu
,上面带有l:libopenblas
的代码将起作用。我仍然想知道是否有某种(理想情况下是运行时)机制来检测c文件中应该使用哪个名称。我终于想出了一个丑陋的破解方法。我不确定它是否能一直工作,但至少它能在Windows(mingw和visual studio)、Linux、MKL和OpenBlas的协同应用中工作。我仍然想知道是否有更好的替代方案,但如果没有,这就可以了:
编辑:立即为visual studio更正
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext
import numpy
from sys import platform
import os
try:
blas_path = numpy.distutils.system_info.get_info('blas')['library_dirs'][0]
except:
if "library_dirs" in numpy.__config__.blas_mkl_info:
blas_path = numpy.__config__.blas_mkl_info["library_dirs"][0]
elif "library_dirs" in numpy.__config__.blas_opt_info:
blas_path = numpy.__config__.blas_opt_info["library_dirs"][0]
else:
raise ValueError("Could not locate BLAS library.")
if platform[:3] == "win":
if os.path.exists(os.path.join(blas_path, "mkl_rt.lib")):
blas_file = "mkl_rt.lib"
elif os.path.exists(os.path.join(blas_path, "mkl_rt.dll")):
blas_file = "mkl_rt.dll"
else:
import re
blas_file = [f for f in os.listdir(blas_path) if bool(re.search("blas", f))]
if len(blas_file) == 0:
raise ValueError("Could not locate BLAS library.")
blas_file = blas_file[0]
elif platform[:3] == "dar":
blas_file = "libblas.dylib"
else:
blas_file = "libblas.so"
## https://stackoverflow.com/questions/724664/python-distutils-how-to-get-a-compiler-that-is-going-to-be-used
class build_ext_subclass( build_ext ):
def build_extensions(self):
compiler = self.compiler.compiler_type
if compiler == 'msvc': # visual studio
for e in self.extensions:
e.extra_link_args += [os.path.join(blas_path, blas_file)]
else: # gcc
for e in self.extensions:
e.extra_link_args += ["-L"+blas_path, "-l:"+blas_file]
build_ext.build_extensions(self)
setup(
name = "wrapped_cfun",
packages = ["wrapped_cfun"],
cmdclass = {'build_ext': build_ext_subclass},
ext_modules = [Extension("wrapped_cfun.cython_part", sources=["pyfile.pyx"], include_dirs=[numpy.get_include()], extra_link_args=[])]
)
除了依靠链接器/加载程序提供正确的blas功能外,另一种方法是模拟必要blas符号的分辨率(例如,
ddot
)并在运行时使用包装的blas符号
不确定,这种方法优于“正常方式”的建筑,但希望引起您的注意,即使只是因为我觉得这种方法很有趣
简而言之,这个想法是:
ddot
-功能的显式函数指针,在下面的代码段中称为my\u ddot
my_ddot
-指针,您将在其中使用ddot
-否则ddot\u mult
:
import numpy as np
a=np.arange(4, dtype=float)
ddot_mult(a,a) # 14.0 as expected!
这种方法的一个优点是,使用distutils时不会有麻烦,而且您可以保证使用与scipy相同的blas功能
另一个好处是:可以在运行时切换使用过的引擎(mkl、open_blas甚至自己的实现),而无需重新编译/重新链接
另一方面,还有一些额外的样板代码,还有一些危险,一些符号的初始化将被遗忘。scipy allready包装了blas函数,您可以直接使用它,但这是在Cython(在.pyx文件中)中使用的。是否可以通过某种方式将其传递给.C文件中的C函数?我认为如果您使用
库
-选项,则无需构造正确的名称-distutils将为您提供:
%%cython
# h-file:
cdef extern from *:
"""
// blas-functionality,
// will be initialized by cython when module is loaded:
typedef double (*ddot_t)(int *N, double *DX, int *INCX, double *DY, int *INCY);
extern ddot_t my_ddot;
double call_ddot(double* a, double* b, int n);
"""
ctypedef double (*ddot_t)(int *N, double *DX, int *INCX, double *DY, int *INCY)
ddot_t my_ddot
double call_ddot(double* a, double* b, int n)
# init the functions of the c-library
# with blas-function provided by scipy
from scipy.linalg.cython_blas cimport ddot
my_ddot=ddot
# a simple function to demonstrate, that it works
def ddot_mult(double[:]a, double[:]b):
cdef int n=len(a)
return call_ddot(&a[0], &b[0], n)
#-------------------------------------------------
# c-file, added so the example is complete
cdef extern from *:
"""
ddot_t my_ddot;
double call_ddot(double* a, double* b, int n){
int one = 1;
return my_ddot(&n, a, &one, b, &one);
}
"""
pass
import numpy as np
a=np.arange(4, dtype=float)
ddot_mult(a,a) # 14.0 as expected!