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

OperationLatencyNotes
FFI call (horus_get_abi_version)11 nsBaseline overhead
CmdVel send (16 bytes)15 nsSmall message
LaserScan send (1472 bytes)~20 nsZero-copy loan
Scheduler tick (empty)37 nsNo nodes
Scheduler tick (1 node)248 nsMedian
Scheduler tick (10 nodes)2.2 usMedian
Scheduler tick (50 nodes)10.9 usMedian
Throughput2.89M ticks/sec1 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"); }
};