Source files at github.com:casperdcl/ctypes-demo, based on bitbucket.org:chris_richardson/ctypes-demo
#include "math.h"
void add(double *in_data, double *out_data, int n)
{
int i;
for (i = 0; i != n; ++i)
out_data[i] += in_data[i];
}
gcc -shared -fPIC -o mylib_shared.so mylib.c
nm -a mylib_shared.so # list symbols
ctypes
from ctypes import CDLL, POINTER, c_double
import numpy as np
MYLIB = CDLL("./mylib_shared.so")
def get_ptr(np_arr, dtype=c_double):
return np_arr.ctypes.data_as(POINTER(dtype))
def add(x, y, n):
assert x.dtype == y.dtype == np.float64
MYLIB.add(get_ptr(x), get_ptr(y), n)
Use as:
import numpy as np
import mylib
x = np.arange(3, dtype=numpy.float64)
y = x.copy()
mylib.add(x, y, 3)
print(y)
cffi
from cffi import FFI
ffi = FFI()
ffi.cdef("void add(double *in_data, double *out_data, int n);")
MYLIB = ffi.dlopen("./mylib_shared.so")
def add(x, y, n):
MYLIB.add(
ffi.cast("double *", x.ctypes.data),
ffi.cast("double *", y.ctypes.data),
n)
Alternative in-place:
from cffi import FFI
ffi = FFI()
ffi.cdef("void add(double *in_data, double *out_data, int n);")
ffi.set_source("mylib_shared", open("mylib.c").read())
ffi.compile(verbose=True)
from mylib_shared import lib as MYLIB
add = MYLIB.add
Use as with ctypes
.
numba
Compile python itself:
from numba import jit
@jit
def add(...):
...
Advanced:
from numba import jit
import numpy as np
@jit
def calculate_distances(n):
pts = np.random.random((n, 3))
d = np.zeros((n, n))
for i in range(n):
for j in range(n):
d[i, j] = np.linalg.norm(pts[i,:] - pts[j,:])
return d
n = 1000
d = calculate_distances(n)
llvm = calculate_distances.inspect_llvm()
for k, v in llvm.items():
print(k, v)
print(d)
Even more advanced:
# Example demonstrating numba cfunc to provide a compiled callback kernel
from ctypes import POINTER, c_double, c_int
from numba import cfunc, types, carray, jit
import numpy as np
# This is the kernel we want to call from our bigger library
def calculate_distances(_d, _pts, n):
d = carray(_d, (n, n), dtype=np.float64)
pts = carray(_pts, (n, 3), dtype=np.float64)
for i in range(n):
for j in range(n):
d[i, j] = np.linalg.norm(pts[i, :] - pts[j, :])
# C signature
sig = types.void(
types.CPointer(types.double),
types.CPointer(types.double),
types.intc)
fn = cfunc(sig, nopython=True)(calculate_distances) # compile with LLVM
# Take a look at the code
print(fn.inspect_llvm())
# Show the memory address of the function
print('0x%0x'%fn.address)
n = 12
d = np.zeros((n, n), dtype=np.float64)
pts = np.random.random((n, 3))
# Convenient ctypes wrapper allows us to test it from Python
fn.ctypes(d.ctypes.data_as(POINTER(c_double)),
pts.ctypes.data_as(POINTER(c_double)), n)
np.set_printoptions(precision=2)
print(d)
class Foo{
public:
std::vector<double> dist(){ ... }
};
pybind11
Replacement for boost::python
#include <cmath>
#include <vector>
class PointCloud {
public:
PointCloud(std::vector<double> &points) : _points(points) {}
std::vector<double> calculate_distances() {
unsigned int n = _points.size() / 3;
std::vector<double> result(n * n);
for (unsigned int i = 0; i != n; ++i)
for (unsigned int j = 0; j != n; ++j)
result[i * n + j] = distance(i, j);
return result;
}
private:
double distance(unsigned int i, unsigned int j) {
double d = 0.0;
for (unsigned int k = 0; k != 3; ++k) {
double d0 = _points[i * 3 + k] - _points[j * 3 + k];
d += d0 * d0;
}
return std::sqrt(d);
}
std::vector<double> _points;
};
#include <point_cloud.cpp> // bad practice ...
// ... you should have a header and link libs instead, but you get the idea
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
// Wrappers using pybind11
namespace py = pybind11;
PYBIND11_MODULE(point_cloud, m) {
py::class_<PointCloud>(m, "PointCloud") // class
.def(py::init<std::vector<double> &>()) // initialiser
.def("calculate_distances", &PointCloud::calculate_distances);
}
from distutils.core import setup
from distutils.extension import Extension
import pybind11
pybind11_include = pybind11.get_include()
setup(name='point_cloud',
version='1.0',
ext_modules=[Extension(
"point_cloud",
['wrap_point_cloud.cpp'],
language='c++',
#include_dirs=[pybind11_include, '/usr/include/eigen3'],
include_dirs=[pybind11_include],
extra_compile_args=['-std=c++11'])])
Use:
python setup.py build
PYTHONPATH="$PYTHONPATH:$PWD/build/lib*" python -c "
from point_cloud import PointCloud
import numpy as np
n = 5
pts = np.random.random((n, 3))
p = PointCloud(pts.flatten().tolist()) # list -> std::vector
d_list = p.calculate_distances() # std::vector -> list
d = np.array(d_list).reshape((n, n)) # Reshape with numpy
print(d)
"
swig
Best for multi-language interfaces, not great if only wrapping for Python.
pycuda
Similar to in-place cffi
.