动机、参考资料、涉及内容

动机

  • pytorch 中的 C++ 代码是怎么跟 python 代码交互的,怎么用源码安装pytorch(研究清楚可能困难比较大)
  • SPTAGfaiss 这两个项目是怎么用 swig 来得到 python 接口的
  • cython, ctypes 等模块的使用
  • Hanlp 1.7.x 版本 java 代码是怎么与python进行绑定的

原生方式

此方法为原生支持

一个示例

计算素数的个数,使用 C 实现,并将其封装为一个 Python 包来进行调用。

素材完全来自博客

c_extension/
  fastcount.c
  setup.py
test.py

fastcount.c 文件内容如下:

#include <Python.h>
// #include <stdio.h>

int c_prime_counter(int frm, int til) {
  // 有些奇怪: 如果不加上 \n, 后续调用就不会打印
  // printf("from: %d, to %d\n", frm, til);
  int primecount = 0;
  for (int num = frm; num <= til; num++) {
    int flag = 0;
    if (num > 1) {
      for (int candidate = 2; candidate < num; candidate++) {
        if ((num % candidate) == 0) {
          flag = 1;
          break;
        }
      }
      if (flag == 0) {
        primecount++;
      }
    }
  }
  return primecount;
}

static PyObject *py_primecounter(PyObject *self, PyObject *args) {
  // Declare two pointers
  int *n_frm, *n_til = NULL;
  // Parse arguments - expects two integers that will be mapped to n_frm and n_til
  // 注意此处传参为指针的指针, 为什么? 感觉跟 CUDA 的 cudaMalloc 函数有点像
  if (!PyArg_ParseTuple(args, "ii", &n_frm, &n_til)) {
    return NULL;
  }
  // Call c-function
  // 这里的调用有些奇怪, 为什么不是传 *n_frm 与 *n_til
  int found_primes = c_prime_counter(n_frm, n_til);
  return PyLong_FromLong(found_primes);
}

static PyMethodDef CountingMethods[] = {
  {"primecounter", py_primecounter, METH_VARARGS, "Function for counting primes in a range in c"},
  {NULL, NULL, 0, NULL}
};


static struct PyModuleDef fastcountmodule = {
  PyModuleDef_HEAD_INIT,
  "Fastcount", // module name
  "C library for counting fast",
  -1,
  CountingMethods
};


PyMODINIT_FUNC PyInit_Fastcount(void) {
  return PyModule_Create(&fastcountmodule);
};

setup.py 文件内容如下:

from distutils.core import setup, Extension


def main():
  setup(
    name="Fastcount",
    version="1.0.0",
    description="Fastcount module in python",
    author="Mike",
    author_email="mikehuls42@gmail.com",
    ext_modules=[Extension("Fastcount", ["fastcount.c"])]
  )

if (__name__ == "__main__"):
  main()

test.py 文件内容如下:

import Fastcount
import time
def py_prime_counter(frm, til):
    primecount = 0
    for num in range(frm, til+1):
        flag = 0
        if num > 1:
            for candidate in range(2, num):
                if (num % candidate) == 0:
                    flag = 1
                    break
            if flag == 0:
                primecount += 1
    return primecount

a, b = 1, 20000
t1 = time.time()
result = Fastcount.primecounter(a, b)
t2 = time.time()
print(f"[c] result:{result}, time: {t2 - t1:.2f}")

t1 = time.time()
result = py_prime_counter(a, b)
t2 = time.time()
print(f"[python] result:{result}, time: {t2 - t1:.2f}")

使用方式如下:

cd c_extension
python setup.py install
cd ..
python test.py

ctypes

待完善与合并

Cython

pybind11

一个最简的例子

来源于官方文档

目录结构

example.cpp
test.py

example.cpp 文件内容

#include <pybind11/pybind11.h>

namespace py = pybind11;

int add(int i, int j) {
    return i + j;
}

// PYBIND11_MODULE 是一个宏, 注意这里的 example 应与文件名对应, 参考:
// https://stackoverflow.com/questions/49111874/creating-a-python-package-for-c-code-wrapped-with-pybind11-using-setuptools
PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring

    m.def("add", &add, "A function that adds two numbers",
        py::arg("i"), py::arg("j"));
}

test.py 文件内容

# 编译为(适配python导入的)动态链接库后,可以直接import
import example
print(example.add(1, j=2))

运行方式为

# 编译得到类似 example.cpython-39.x86_64-linux-gnu.so 的动态链接库文件
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

python test.py