Python笔记:外部c函数调用

Python笔记:外部c函数调用

序言

前些时候,一个朋友突然问我:python做计算实在是太慢了,有什么办法可以加速python的运算吗?我说:简单啊,你直接调用外部c函数就行了,我印象中cython可以直接实现的。闻言,我那个朋友喜出望外,遂言:太好了,那你给我写个demo呗。。。

emmmm。。。

好吧,我承认我之前事实上只是知道可以这么做,真的要说实现。。。

唉,自己挖的坑,流着泪也要把它填平了。于是,趁着周末两天,我网上找了一些demo,然后自己实现了几种python调用外部c函数的实现方式。

不要问我为啥今天才发出来,问就是打字慢。

下面,话不多说,上干货!

1. ctypes实现

c_types实现大约是最简单的外部c函数实现方法了,你只需要准备写好你的C函数实现,然后编译,最后调用就行了,无需任何中间文件,一切都是如此简单。

下面,我们以a到b的连续求和函数作为例子来进行说明:

  1. step 01: 写作c代码实现

    我们在my_func.c脚本中实现我们的代码如下:

    int myfunc(int a, int b){
        int ans = 0;
        for(int i=a; i<=b; ++i){
            ans += i;
        }
        return ans;
    }
    
  2. step 02: 编译脚本

    我们调用如下命令即可:

    gcc -shared -o mylib.so myfunc.c
    
  3. step 03: 调用测试

    最后,我们只要在python代码中使用如下方法调用即可:

    from ctypes import CDLL
    mylib = CDLL("./mylib.so")
    mylib.myfunc(0, 100) # 5050
    

完事,收工,一切都是如此顺利!

2. cython实现

较之ctypes实现方法,cython方法会更加复杂一点,它不需要依赖于ctypes库,而是直接将c代码转译为python底层c实现中可读的代码,而后将这一部分封装为一个动态链接库。

因此,在这种情况下,我们完全可以将这个生成的动态链接库当成一个普通的python包来进行调用,其执行效率上也会优于ctypes方式的调用。

下面,我们来考察其具体实现。

  1. step 01: 准备c函数实现

    这部分完全同上,这里不再赘述。

  2. step 02: 配置动态链接库

    我们在外准备一个mylib.pyx文件用于配置我们需要封装的动态链接库。

    cdef extern from "myfunc.c":
        cpdef int myfunc(int a, int b)
    
  3. step 03: 动态链接库生成

    在准备好了动态链接库配置文件之后,我们构建一个setup.py文件来生成我们的动态链接库。

    setup.py文件内容如下:

    from distutils.core import setup, Extension
    from Cython.Build import cythonize
    
    ext_modules=[
        Extension("mylib", sources=["mylib.pyx"])
    ]
    
    setup(
        name = 'wrapper for myfunc',
        ext_modules = cythonize(ext_modules)
    )
    

    而后,我们调用下述命令进行调用:

    python setup.py build_ext --inplace
    

    操作完成之后,我们即可得到一个.so动态链接库文件,该文件即为我们可调用的python包。

  4. step 04: 调用测试

    我们使用如下代码即可进行调用测试。

    from mylib import myfunc
    
    myfunc(0, 100) # 5050
    

由是,cython方法搞定收工!

3. c extension实现

注意到,cython方式构建动态链接库过程中,会调用cythonize函数,而这个函数会先生成一个.c中间文件,而这个中间文件即为我们的动态链接库中真实包含的c函数代码实现。

事实上,后续的setup函数就是针对这个.c中间文件进行编译并构建为动态链接库。

因此,我们可以绕过cythonize函数,直接自己来构建这个.c文件,然后进行动态链接库的构建。

而这,就是c extension方法的主要思路。

同样的,给出其操作流程如下:

  1. step 01: c函数准备

    这部分内容见上即可。

  2. step 02: 配置动态链接库

    这里,我们直接给出动态链接库的配置代码示例如下:

    // mylib.c
    #include <Python.c>
    #include "myfunc.c" // 自定义c函数库,包含目标函数myfunc
    
    // 转义c函数
    static PyObject * demo(PyObject *self, PyObject *args){
        int a, b;
        if (!PyArg_ParseTuple(args, "ii", &a, &b))
            return NULL;
        return Py_BuildValue("i", f2(a, b));
    }
    
    // 定义动态链接库中包含的函数
    static PyMethodDef DemoMethods[] = { 
        {"myfunc", demo, METH_VARARGS, "my function"},
        {NULL, NULL, 0, NULL} 
    };
    
    // 定义动态链接库
    static struct PyModuleDef cModPyDem =
    {
        PyModuleDef_HEAD_INIT,
        "mylib",     // 导出后的动态链接库名称
        "",          
        -1,          
        DemoMethods
    };
    
    // 构建动态链接库函数接口,后续的setup.py脚本将会调用这一函数进行生成
    PyMODINIT_FUNC PyInit_mylib(void){
        return PyModule_Create(&cModPyDem);
    }
    
  3. step 03: 动态链接库构建

    动态链接库的构建方法与上述cython方法相仿,我们只需要给出一个setup.py脚本并进行调用即可。

    给出其内容如下:

    from distutils.core import setup, Extension
    
    module = Extension("mylib", sources=["mylib.c"])
    
    setup(name = "build my c library", ext_modules=[module])
    

    而后,我们同样采用下述命令编译即可:

    python setup.py build_ext --inplace
    
  4. step 04: 调用测试

    由于这一方式与上述cython方式本质上是完全相同的,因此,其调用方法也完全相同,我们对此不再赘述。

综上,c extension方法搞定!

4. swig实现

swig也是常用的python调用外部c函数的实现方法之一,其核心与上述cython完全相似,唯一的区别点在于,cython方法使用cython库来进行代码转义,而这里使用swig进行代码转义。。。

我们给出其操作流程如下:

  1. step 01: c函数实现

    bala,bala,bala。。。

  2. step 02: 配置动态链接库

    使用swig进行动态链接库的配置所需要提供的配置文件为.i文件,其内容格式如下:

    %module mylib
    
    %{
    #define SWIG_FILE_WITH_INIT
    #include "myfunc.c"
    %}
    
    int myfunc(int a, int b);
    

    而后,我们调用下述命令即可生成实际的.c动态链接库配置脚本:

    swig -python mylib.i
    
  3. step 03: 构建动态链接库

    在上述步骤后,我们会得到一个名为mylib_wrap.c的文件,而后,我们仿照c extension方法给出下述构建脚本setup.py即可:

    from distutils.core import setup, Extension
    
    module = [
        Extension('mylib', sources=['myfunc.c', 'mylib_wrap.c'])
    ]
    
    setup (name = 'mylib', ext_modules = module)
    

    执行如下编译命令即可得到最终的.so动态链接库文件:

    python setup.py -build_ext --inplace
    
  4. step 04: 调用测试

    da…da…da…

至此,swig方法搞定!

5. 效果测试 & 结论

现在,我们来比较一下上述各个方法调用外部c函数的性能。

我们重复执行 1 0 7 10^7 107次计算1到100的连续求和,得到各个方法的耗时如下:

方法 耗时
python ~50s
ctypes ~7s
cython 1.88s
c extension 2.76s
swig 2.41s

结论:

  1. 上述4种方式实现c函数外部调用确实能给python带来极大的性能提升;
  2. 就实现方式来说,ctypes是最容易实现的,但是相对的,其执行效率也是4种方法中最慢的;
  3. c extension、cython以及swig三种实现方法本质上来说是同一种实现方法,其外部c函数调用的执行速度上没有量级上的差异,但是从其实际的效果来看,cython方式相对而言操作更为简单,其效率也是最高的。

参考文献

[1] 在python里调用C函数的三种方式
[2] python调用c和c++库(直接调用和使用swig)
[3] SWIG and Python

猜你喜欢

转载自blog.csdn.net/codename_cys/article/details/106671567