通过使用回调在Cython代码中封装C库(GSL)

2 投票
2 回答
1389 浏览
提问于 2025-04-18 11:45

我刚接触 cythonc,想用 cython 来加速我的代码性能。我想在我的代码中使用 gsl_integration 库来进行积分。

更新:test_gsl.pyx

cdef extern from "math.h":
    double log(double x) nogil
cdef extern from "gsl/gsl_math.h":
    ctypedef struct gsl_function:
        double (* function) (double x, void * params)
        void * params

cdef extern from "gsl/gsl_integration.h":
    ctypedef struct gsl_integration_workspace
    gsl_integration_workspace *  gsl_integration_workspace_alloc(size_t n)
    void  gsl_integration_workspace_free(gsl_integration_workspace * w)

    int  gsl_integration_qags(const gsl_function * f, double a, double b, double epsabs, double epsrel, size_t limit, gsl_integration_workspace * workspace, double *result, double *abserr)


cdef double do_callback(double x, void* params):
    return (<MyCallback>params).eval(x)

cdef class MyCallback:
    cdef double a
    def __init__(self, a):
        self.a = a
    cpdef double eval(self, double x):
        return self.a * log(x+1) * x
    def call_gsl(self):
        cdef gsl_integration_workspace* w =gsl_integration_workspace_alloc (1000)

        cdef gsl_function F
        F.function = &do_callback
        F.params = <void*>self

        cdef double result = 3, error = 5
        gsl_integration_qags(&F, 0, 1, 0, 1e-7, 1000, w, &result, &error)
        print result, error
        gsl_integration_workspace_free(w)

这个 .pyx 文件是用下面的 setup.py 文件编译的,并且没有出现任何错误信息:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy
import sys 
ext = Extension("test_gsl", ["test_gsl.pyx"],
    include_dirs=[numpy.get_include(),
                  "/usr/include/"],
    library_dirs=["/usr/lib/"],
    libraries=["gsl"])

setup(ext_modules=[ext],
    cmdclass = {'build_ext': build_ext})

甚至可以通过命令行来编译:

cython test_gsl.pyx
gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/vol/anaconda/lib/python2.7/site-packages -I/usr/include -I/vol/anaconda/include/python2.7 -c test_gsl.c `pkg-config --cflags gsl` 
gcc -pthread -shared test_gsl.o -L/usr/lib -L/vol/anaconda/lib -lgsl -lgslcblas -lpython2.7 `pkg-config --libs gsl` -o test_gsl.so

但是当在 Python 中这样导入时,却出现了错误:

>>> import pyximport; pyximport.install()
(None, <pyximport.pyximport.PyxImporter object at 0x7f0c7e888150>)
>>> import gsl
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda/lib/python2.7/site-packages/Cython-0.20.1-py2.7-linux-x86_64.egg/pyximport/pyximport.py", line 431, in load_module
    language_level=self.language_level)
  File "/anaconda/lib/python2.7/site-packages/Cython-0.20.1-py2.7-linux-x86_64.egg/pyximport/pyximport.py", line 210, in load_module
    mod = imp.load_dynamic(name, so_path)
ImportError: Building module gsl failed: ['ImportError: /users/dalek/.pyxbld/lib.linux-x86_64-2.7/gsl.so: undefined symbol: gsl_integration_qags\n']

gsl_integration_qags 已经正确定义了,我不明白为什么还会出现这个错误?

2 个回答

2

第一条规则:过早优化是万恶之源。

第二条规则:无论如何都要遵循第一条规则。

第三条规则:如果没有必要,就不要使用C++的复杂特性(相对于C来说的复杂性,包括类)。如果你在和C库混合使用C++时,这一点尤其重要。

在你的例子中,我看不出为什么需要使用C++类,特别是因为这样做会引入一个不必要的间接层(包装器)!如果你在用编译语言追求性能,避免不必要的步骤和间接层正是你想要的!你让自己的生活变得复杂,尤其是因为C语言中的GSL例程将完成你程序中99.9%的计算。为什么不直接使用像 cython-gsl 这样的东西,把你的代码简化成类似的形式(这是从cython-gsl示例文件夹中取的)。这样写更短、更简洁,我看不出为什么它的性能不好,尤其是考虑到Python并没有进行任何重的工作(假设函数foo()会被转换成C,这似乎是事实)!

from cython_gsl cimport *

ctypedef double * double_ptr ctypedef void * void_ptr

cdef double foo(double x, void * params) nogil:
    cdef double alpha, f
    alpha = (<double_ptr> params)[0]
    f = log(alpha*x) / sqrt(x)
    return f


def main():
    cdef gsl_integration_workspace * w
    cdef double result, error, expected, alpha
    w = gsl_integration_workspace_alloc (1000)

    expected = -4.0
    alpha = 1

    cdef gsl_function F
    F.function = &foo
    F.params = &alpha

    gsl_integration_qags (&F, 0, 1, 0, 1e-7, 1000, w, &result, &error)
    print "result          = % .18f\n" % result
    print "estimated error          = % .18f\n" % error
1

在用下面的命令编译之后:

cython test_gsl.pyx
gcc -m64 -pthread -fno-strict-aliasing -Wstrict-prototypes -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/include -I/vol/dalek/anaconda/include/python2.7 -c test_gsl.c -o build/temp.linux-x86_64-2.7/test_gsl.o
gcc -pthread -shared -L/usr/lib/ -L/vol/dalek/anaconda/lib -o test_gsl.so  build/temp.linux-x86_64-2.7/test_gsl.o -lpython2.7  -lgsl -lgslcblas -lm

如果我在Python中导入 test_gsl,而不使用 pyximport,那它就能正常工作。因为 pyximport 不支持链接外部库。

撰写回答