An efficient method of calling C++ functions from numba using clang++/ctypes/rbc
Published October 19, 2021
While there exist ways to wrap C++ codes to Python (see Appendix below), calling these wrappers from Numba compiled functions is often not as straightforward and efficient as one would hope.The underlying problem for this inefficiency is that the Python/C++ wrappers are designed to be called with Python objects as inputs. For instance, with keeping in mind the aim of this post, a call to a Numba compiled function would involve converting a Python object to a low-level object, say, to some C/C++ equivalent intrinsic type or structure, then converting it back to Python object to be passed to the Python/C++ wrapper function. Then this wrapper function would convert the Python object (again) to an equivalent C/C++ type object which can be finally passed to the underlying C++ library function. The return value of the C++ function would be transformed several times as well, just in the opposite direction of the function's calling sequence. In totality, all these object transformations may build up a considerable overhead of calling otherwise highly efficient C++ library functions from Python.
Another difficulty with calling C++ library functions from Numba originates from the name-mangling that C++ compilers apply to function names in order to support function/method overloadings as well as other relevant C++ language features. In principle, one would be able to call such a C++ library function directly from any Numba compiled function (using the numba.cfunc feature) if one would knows how the C++ compiler transforms the function name internally . However, the name-mangling algorithm is C++ compiler vendor dependent, and in practice, it would be hard to predict the mangled name in a portable manner.
In this post, we'll introduce a straightforward and efficient method
of calling C++ library functions from Numba compiled functions that
circumvents the name-mangling problem. The method is based on
determining the addresses of C++ library functions at runtime which
together with functions signatures are then used to set up a highly
efficient calling sequence. This method will require creating a small
C/C++ wrapper library that contains
functions which return the addresses of C++ library functions and
can be easily called from Python using various techniques, here we use
We provide here a Python script cxx2py.py that
auto-generates, from a user-supplied C++ header and source files, the
C/C++ wrapper library as well as a Python
module. The Python module contains
ctypes definitions of C++
library functions that Numba compiled functions are able to call
directly without requiring the expensive and redundant object
transformations mentioned above. An alternative to using
the Python wrapper module would be to use the Numba Wrapper Address
. Its usage should be considered if the
expressiveness" turns out to be insufficient.
Currently, the supported features in the
cxx2py.py tool include:
- wrapping of C++ library functions with scalar inputs and return values,
- supporting C++ functions which may be defined inside C++ namespaces,
- supporting C++ functions which may be static class member functions.
cxx2py.py tool can be extended to support other C++ features
- creating C++ class/struct instances from Python,
- passing C++ class/struct instances to-and-fro between languages,
- calling the methods of C++ class/struct instances,
- supporting pointer types as function inputs and return values,
cxx2py.py tool uses CLang to parse
C++ header files and to build the C/C++ wrapper shared library. It
also uses the RBC - Remote Backend
Compiler to parse the signatures
of C++ functions for convenience.
As a prerequisite, let's create a Conda environment as follows
We assume that the cxx2py.py script is copied to the current working directory and is functional:
Next, let us consider the following C++ header and source file that we will use as a model of a C++ library:
We build a Python ctypes wrapper library using
that will create three files in the current directory:
Notice that the generated cxx2py_libfoo.cpp file contains light-weight C functions for returning the addresses of C++ functions:
cxx2py_libfoo.cpp file is built into the shared library
--build flag is used.
Let's test the wrapper module libfoo in Python:
that is, the C++ library functions can be called directly from Python thanks to ctypes!.
Moreover, the C++ library functions can be called from Numba compiled functions. For example:
In this post, we outlined a method of calling C++ library functions from Python with an emphasis on their usage from Numba compiled functions with minimal overhead. While the provided tool cxx2py.py currently supports only wrapping C++ functions with scalar inputs and return values, it can be easily extended to support other C++ features as well.
I thank Breno Campos and Guilherme Leobas for discussions and for sharing their expertise on the LLVM Project.
Appendix: A list of Python/C/C++ connectivity tools
- Boost.Python: C++ library with a IDL-like interface for binding C++ classes and functions to Python
- cffi: C Foreign Function Interface for Python
- cppyy: Automatic Python-C++ bindings
- ctypes: A foreign function library for Python
- Cython: C-Extensions for Python
- pybind11: Seamless operability between C++11 and Python
- Python C/API: Extending and Embedding the Python Interpreter
- SIP: Python bindings generator to C or C++ libraries
- SWIG: Simplified Wrapper and Interface Generator