Greetings everyone,
continuing with my particle simulation, I am trying to turn it into a python package (I work in maths, and most people would never be able to use my code if it doesn't come wrapped in either Python or Matlab lol).
I set everything up with PyBind11. I hope some of you are familiar with this.
I essentially have two classes.
model<size_t DIMENSION>
specifying the model (eg. how many particles, how they interact etc.) and a simulation class
simulation <size_t DIMENSION>
which does all the computations. The simulation gets passed the model as a reference. The template parameter DIMENSION may be 1, 2, or 3.
The idea now is to expose the simulation class, mainly its
run()
function, to python, such that a user just needs to do
1 2 3 4 5 6 7
|
import my_module
// specify some args
simu = Simulation(/* args */)
simu.run()
|
The problem is that the run function when called like this in Python is roughly 30 to 50% slower than when I simply set up the simulation with a C++ main.cpp file. All the heavy lifting is done within the run function and there is no explicit Python-C++ interaction happening within.
My pybind file looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
#include <pybind11/pybind11.h>
#include "simulation.h"
#include "model.h"
#include <string>
#include <memory>
#include <variant>
namespace py = pybind11;
// using variant because my model comes in 1D, 2D, and 3D versions.
using ModelVariant = std::variant<
std::unique_ptr<model<1>>,
std::unique_ptr<model<2>>,
std::unique_ptr<model<3>>
>;
// same for simulation
using SimuVariant = std::variant<
std::unique_ptr<simulation<1>>,
std::unique_ptr<simulation<2>>,
std::unique_ptr<simulation<3>>
>;
// Wrapper class exposed to Python.
class PythonWrapper {
public:
PythonWrapper(int dim, /* other args*/)
{
if (dim == 1){
model_variant = std::make_unique<model<1>>(/*args*/);
simulation_variant= std::make_unique<simulation<1>>( *std::get<std::unique_ptr<model<1>>>(model_variant), /* other args */);
}
else if (dim == 2) {
model_variant = std::make_unique<model<2>>(/*args*/);
simulation_variant = std::make_unique<simulation<2>>( *std::get<std::unique_ptr<model<2>>>(model_variant), /* other args */);
}
else if (dim == 3) {
model_variant = std::make_unique<model<3>>(/*args*/);
simulation_variant = std::make_unique<simulation<3>>( *std::get<std::unique_ptr<model<3>>>(model_variant), /* other args */);
}
else throw std::runtime_error("Unsupported dimension. Only 1D, 2D, and 3D are allowed.");
}
void run() {
std::visit([](auto& simulation) { simulation->run(); }, simulation_variant); // This will be much slower when run from Python for some reason.
}
private:
ModelVariant model_variant;
SimuVariant simulation_variant;
};
// make wrapper class known to Python
PYBIND11_MODULE(my_module, m) {
// Bind wrapper class.
py::class_<PythonWrapper>(m, "Simulation")
.def(py::init</*args*/>())
.def("run", &PythonWrapper::run)
}
|
Compilation happens via
g++ -O3 -fopenmp -shared -fPIC $(python3 -m pybind11 --includes) -o my_module.so src/pybind.cpp $(python3-config --ldflags)
The usual C++ source code using a main.cpp is compiled via
g++ -O3 -fopenmp -o my_simu src/main.cpp
Does anyone have an idea whether I am doing something wrong?