To accelerate the execution of some performance critical code, we can write the code in C++ with the help of pybind11 and export the C++ code as shared library1. Then we can import the shared library as a module and enjoy speed boost.

Install pybind11:

python3 -m pip install pybind11

A simple code using pybind11

A simple example using pybind11 is shown below:

// include pybind11 header files so that we can use PYBIND11_MODULE macro
#include <pybind11/pybind11.h>

namespace py = pybind11;

int sum(int start, int end){
    // calculate sum from i to j
  if (start > end) return 0;

  int sum = 0;
  for (int i = start; i <= end; i++){
    sum += i;
  }

  return sum;
}

PYBIND11_MODULE(demo, m) {
    m.doc() = "pybind11 demo plugin"; // optional module docstring

    m.def("sum", &sum, "calculate sum from start to end",
        py::arg("start") = 1, py::arg("end") = 1000);
}

In the above code, we use macro PYBIND11_MODULE to define a module, whose name is demo and represented as m. m.def() is used to register function to this module:

  • The first parameter is the function name you want to use. It does not need to be the same with the C++ function name.
  • Second parameter is the function address (&sum).
  • Third parameter is the function documentation.
  • py:arg() is used to add keyword argument and its default values to the function.

Compile the C++ code

On the command line

We can directory compile the code on the command line:

c++ -O3 -Wall -shared -std=c++11 -fPIC -I$(python3 -m pybind11 --includes) demo.cc -o demo$(pyton3-config --extension-suffix)

In the above command, python3 -m pybind11 --includes is used to get the relevant include path for pybind11. On my system, the output is like the following:

-I/Users/jdhao/tools/miniconda3/include/python3.9 -I/Users/jdhao/tools/miniconda3/lib/python3.9/site-packages/pybind11/include

The command python3-config --extension-suffix is used to get the proper suffix for shared library based on current system.

Compile using Makefile

It would be tedious to type the above command each time to compile the code. We can write a simple Makefile to simplify the work:

INCLUDE := $(shell python3 -m pybind11 --includes)
FLAG := -O3 -Wall -shared -std=c++11 -fPIC
SUFFIX := $(shell python3-config --extension-suffix)
demo:
    c++ $(FLAG) $(INCLUDE) demo.cc -o demo$(SUFFIX)

Then use make demo to compile.

Import and run the code

Open a Python interpreter and we can use the module just like other Python modules:

import demo

print(demo.sum())  # use the default parameter value

print(demo.sum(start=1, end=100))

References


  1. Of course, there are other ways to run C++ code in Python. ↩︎