C++ Performance Guide
Zero-Copy Publishing
The loan pattern gives you a direct pointer into shared memory:
auto pub = sched.advertise<horus::msg::LaserScan>("lidar.scan");
auto sample = pub.loan(); // ~3ns — get SHM buffer
sample->ranges[0] = 1.5f; // 0ns — direct write to SHM
sample->ranges[1] = 2.0f; // 0ns — no copies
pub.publish(std::move(sample)); // ~3ns — make visible to subscribers
// Total: ~6ns regardless of message size (LaserScan is 1472 bytes)
vs. send by copy:
horus::msg::LaserScan scan{};
scan.ranges[0] = 1.5f;
pub.send(scan); // ~15ns — copies 1472 bytes to SHM
Rule: Use loan() + publish() for large messages (> 64 bytes). Use send() for small messages (CmdVel = 16 bytes).
TensorPool for Large Data
For camera images and point clouds, use pool-backed types:
horus::TensorPool pool(1, 64 * 1024 * 1024, 128); // 64MB, 128 slots
// Camera image — allocated from SHM pool, not heap
horus::Image img(pool, 1920, 1080, horus::Encoding::Rgb8);
// 1920 * 1080 * 3 = 6.2MB — zero-copy from pool
// Neural network tensor
uint64_t shape[] = {1, 3, 224, 224};
horus::Tensor tensor(pool, shape, 4, horus::Dtype::F32);
float* data = reinterpret_cast<float*>(tensor.data());
data[0] = 1.0f; // Direct SHM write
Measured Performance
| Operation | Latency | Notes |
|---|---|---|
| FFI call (horus_get_abi_version) | 11 ns | Baseline overhead |
| CmdVel send (16 bytes) | 15 ns | Small message |
| LaserScan send (1472 bytes) | ~20 ns | Zero-copy loan |
| Scheduler tick (empty) | 37 ns | No nodes |
| Scheduler tick (1 node) | 248 ns | Median |
| Scheduler tick (10 nodes) | 2.2 us | Median |
| Scheduler tick (50 nodes) | 10.9 us | Median |
| Throughput | 2.89M ticks/sec | 1 node |
Avoiding Common Pitfalls
Don't allocate in tick:
// BAD — allocates every tick
void tick() override {
auto str = std::string("hello"); // heap allocation
}
// GOOD — pre-allocate
class MyNode : public horus::Node {
char buf_[256]; // stack or member
void tick() override {
std::snprintf(buf_, 256, "hello");
}
};
Don't copy large messages:
// BAD — copies 1472 bytes
horus::msg::LaserScan scan{};
pub.send(scan);
// GOOD — writes directly to SHM
auto sample = pub.loan();
sample->ranges[0] = 1.5f;
pub.publish(std::move(sample));
Pre-create publishers/subscribers:
// BAD — creates Topic in tick (SHM open + mmap)
void tick() override {
horus::Publisher<msg::CmdVel> pub("cmd"); // DON'T
}
// GOOD — create once in constructor
class MyNode : public horus::Node {
horus::Publisher<msg::CmdVel>* pub_;
MyNode() : Node("x") { pub_ = advertise<msg::CmdVel>("cmd"); }
};