Skip to content

cppyy_kit

Prototype in Python, run at C++ speed — mix Python and C++ with ease.

cppyy_kit is a suite of kits that drive real C++ robotics libraries from short Python via cppyy. No bindings to write, no code generation, no build step: the C++ library you already have installed is called directly, its own class and method names intact, while your Python does the orchestration. When a hot path needs C++ speed, you write that path in C++ inline — in the same file — and the kits handle the data marshaling and object lifetime across the boundary.

You get the productivity of a Python prototype and the performance of the C++ library underneath it, and the same code climbs an optimization ladder (freeze the startup cost, cache the compile, lower a hot leaf) without changing shape. Every number on this site is measured on one machine on one day and linked to the row that produced it in Benchmarks.

cppyy_kit logo

Three ways to mix Python and C++

Drive a whole C++ library from Python — the real BehaviorTree.CPP engine parses the XML, owns the tree, and ticks it; there is no Python binding for it, so this capability does not otherwise exist:

import bt_kit
bt = bt_kit.bringup_bt()
tree = bt.BehaviorTreeFactory().create_tree_from_text(xml)   # the C++ factory
tree.tickWhileRunning()                                      # the C++ engine ticks

Or drop to inline C++ for a hot kernel — the decorated function's docstring is its C++ body; its annotations drive the NumPy marshaling; it compiles once and is cached to a real .so thereafter (no first-use JIT after the first build):

import numpy as np
from cppyy_kit import cpp

@cpp
def sum_sq(data: cpp.arr("float")) -> float:      # numpy -> (float* data, size_t data_size)
    "double s = 0; for (std::size_t i = 0; i < data_size; ++i) s += data[i]*data[i]; return s;"

sum_sq(np.array([1, 2, 3], np.float32))            # 14.0 — no ctypes, no build, no bindings

Run those kernels on every core — plain Python threads run in true parallel once a @cpp kernel releases the GIL around its compiled body, which pure-Python threads cannot do. Add nogil=True and the body runs with the interpreter lock dropped; on a 16-core machine, eight independent jobs finish about 7.7× faster than with the GIL held (≈150 ms → ≈20 ms). The runnable example has the full version:

import threading, numpy as np
from cppyy_kit import cpp

@cpp(nogil=True)                                   # the compiled body runs with the GIL released
def crunch(out: "double*", slot: int, iters: int) -> None:
    "double s = 0; for (std::size_t k = 1; k <= (std::size_t)iters; ++k) s += 1.0/(double(k)*1e-3 + 1.0); out[slot] = s;"

out = np.zeros(8)                                                    # 8 independent jobs, one slot each
threads = [threading.Thread(target=crunch, args=(out, i, 20_000_000)) for i in range(8)]
for t in threads: t.start()
for t in threads: t.join()                                          # all 8 run at once, one per core

@cpp(nogil=True) wraps the compiled body in Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS, so the interpreter lock is dropped while the C++ runs — no trick, just the GIL released for the native work; only cppyy's argument/result marshaling stays under the lock. The jobs are independent and write into distinct C++ slots, so none needs the GIL while computing.

The friction these three share — locating and loading the .sos, pinning callback lifetimes, hiding cppyy's template and ownership sharp edges — is factored into the cppyy_kit base, so each domain kit stays thin and its Python mirrors the library's own API 1:1.

Measured results

The Benchmarks page is the single consolidated source (one machine, one day, reproducible commands); each kit's REPORT.md carries the original evidence. Each row below names its benchmark and links to it.

Lever Result
Accelerate — PCL cloud stays in C++ end to end 15.1× latency / 7.4× CPU at 74-LOC parity, in the PCL pipeline benchmark
Freeze — zero-config Cling PCH of the library headers rclcpp bringup ~1.73 s → 0.064 s (~27×) in the auto-PCH measurement, header parse eliminated
Compile cache — content-hashed @cpp/cppdef.so first-use JIT 632 → 91 ms on the PCL VoxelGrid kernel, paid once per machine
Lower (L2) — hot leaf authored as native C++ inline Crocoddyl model 22.9× on the WBC action model, bit-identical
TF ingest — C++ tf2 listener vs Python callback 7.4–16.9× lower ingest CPU, in the TF ingest benchmark

The kits

Kit What it drives Headline
cppyy_kit (base) the ROS-free machinery: load / callback / lifetime, @cpp, require, nogil, freeze & compile-cache first-use JIT paid once per machine: 632 → 91 ms on the PCL VoxelGrid kernel
rclcpp_kit rclcpp (ROS 2 core): bringup, messages, tf, rosbag2, CDR TF ingest 7.4–16.9× lower CPU
bt_kit BehaviorTree.CPP v4 (no Python binding exists) Groot2-compatible trees from Python
pcl_kit Point Cloud Library (no maintained binding) 15.1× latency / 7.4× CPU at LOC parity
ompl_kit Open Motion Planning Library Python validity-checker in the planner's inner loop, no codegen
nav2_kit Nav2 algorithm cores, composed from Python the real RegulatedPurePursuit with no lifecycle servers / no pluginlib
moveit_kit the full MoveIt 2 C++ API the whole C++ surface, not moveit_py's subset
control_kit ros2_control a Python controller in the real controller_manager
cv_kit OpenCV C++ zero-copy Imagecv::Mat, one CUDA branch point
dbow_kit DBoW2 place recognition (no binding, not on conda-forge) loop closure from short Python
wbc_kit Crocoddyl custom action models inline-C++ model, no build system

Each kit is a package with a WHY.md (the rationale), REPORT.md (the evidence), and SKILL.md (the LLM-facing cheat sheet); the anatomy is in Architecture.

Demos & examples

Every headline links to the exact row that produced it in Benchmarks.

Demo What it proves Headline number
Live webcam A vs B a hand-written NCC tracker in one inline-C++ kernel vs the identical NumPy loop 16.18× @ 640×480
IK 5-solver bench benchmark C++-only IK solvers (incl. unpackaged bio_ik/pick_ik) from one Python file pure-Python 10–25× slower; bio_ik 991 solve/s
WBC inline-C++ model a custom Crocoddyl action model authored inline, JIT-compiled, no CMake 22.9× vs Python-derived, bit-identical
Retargeting teleop rig webcam → body/hand tracking → TF → whole-body retarget onto G1/Talos, live, one Rerun viewer glue kernel 341.5×, /tf marshaling 258.9×
Visual loop closure ORB + DBoW2 + GTSAM front-end in short Python, pixels never leave C++ 1080p ingest 135.8×; 19 loops, P/R 1.00/0.95
Jitter bench a ~1 kHz control loop orchestrated from Python on a stock kernel ~2 µs median period, unprivileged
cppyy-accelerate skill point a coding agent at slow Python; it moves the hot path to a kit 16.3× (49.6 → 3.04 ms), bit-identical

Where the speedups apply — and where they don't

The webcam gap is large because the hot per-frame stage is a hand-written per-pixel NCC tracker with no OpenCV one-liner. When the per-frame work is only library-provided ops (ORB, RANSAC — cv2 is already C++), the same A-vs-B comparison narrows to ~1.1–1.2× (webcam report).

In the retargeting rig, the measured cppyy wins are the /tf message marshaling and the transform/retarget kernel. The IK solve runs on pinocchio's own Python bindings: instantiating pinocchio::Model from headers under Cling trips boost 1.90's variant template-arity limit (pinocchio's 25-type joint boost::variant), so that path cannot be JIT-parsed (retarget report).

The benchmarks ran on a shared development machine, so the ratios are more repeatable than the absolute times.

The optimization ladder

The same code climbs rungs as you need more speed — the kit API does not change:

  • Prototype (L0). Plain Python driving the kit. Headers parsed and per-signature wrappers JIT-compiled on first use. Fastest to write.
  • Accelerate. Move the hot path onto C++ via a kit, @cpp, or nogil — 15.1× lower latency in the PCL pipeline benchmark, where the cloud stays in C++ end to end.
  • Freeze. A zero-config Cling PCH of the library headers is built once into ~/.cache/cppyy_kit and auto-loaded thereafter, eliminating the header parse — ~27× on rclcpp bringup (~1.73 s → 0.064 s) in the auto-PCH measurement. The compile cache does the same for @cpp/cppdef kernels: the first-use JIT — 632 → 91 ms on the PCL VoxelGrid kernel — is paid once per machine, not once per process.
  • Lower (L2). A proven-hot leaf is authored as a native C++ node — 22.9× on the WBC Crocoddyl action model (bit-identical cost), removing the per-call cppyy boundary.

Read the full ladder in Freeze & Cache and the 36 documented patterns behind it in The Patterns.

Powers rclcppyy

rclcpp_kit is the capability layer under rclcppyy — the drop-in accelerator that lets an existing rclpy program run ROS 2's C++ core (rclcpp, tf2, rosbag2, CDR serialization) with minimal changes. rclcppyy 0.2.0 is now thin re-export shims over rclcpp_kit, and installs from the same channel as ros-jazzy-rclcppyy.

Built for LLM agents

Agent-consumability is a design goal: every kit ships a SKILL.md (a compact, LLM-facing cheat sheet of its real API), The Patterns is the shared playbook a coding agent reads before writing a new kit or call, and the cppyy-accelerate skill is a Claude-Code-consumable PROFILE → MAP → APPLY → VERIFY procedure whose worked example accelerates a naive voxel downsampler 16.3× with bit-identical output.

Next steps


Origin: extracted and expanded from rclcppyy, which it now powers.