HORUS Benchmarks
Performance validation with real-world robotics workloads.
Benchmark Methodology
Measurement Approach
- Statistical sampling: Criterion.rs micro-benchmarks, sample sizes 10-1000 depending on the group
- Confidence intervals: Bootstrap 95% CI with Tukey IQR outlier detection
- Controlled methodology: 2-10s measurement windows per group, Criterion's default 3s warm-up
- Standalone binaries: RDTSC-timed, 50,000-100,000 samples per scenario
- Comprehensive coverage: 4 robotics message types, 10 backend routes
Workload Testing
- Real workloads: Control loops, sensor fusion, manipulator commands, lidar scans
- Fault injection: Per-node
FailurePolicy(Restart/Skip) recovery testing - Scale testing: Validated up to 100 concurrent nodes and 1,000 topics
- Topology coverage: Same-thread, cross-thread and cross-process, 1:1 through N:N
- Contention: Multi-producer and multi-consumer paths measured under contention
Executive Summary
HORUS delivers sub-microsecond latency for production robotics applications.
Measured with robotics_messages_benchmark on an Intel Core i7-10750H (6C/12T, powersave governor), 50,000 iterations per message type after 5,000 warmup iterations. These are the four message types the benchmark actually runs:
| Message Type | Size | Median | p99 | Throughput | Typical Rate | Headroom |
|---|---|---|---|---|---|---|
| CmdVel | 16 B | 75 ns | 135 ns | 12.14M msg/s | 1000 Hz | 12,140x |
| Imu | 304 B | 121 ns | 235 ns | 7.46M msg/s | 500 Hz | 14,920x |
| JointCommand | 928 B | 135 ns | 226 ns | 6.89M msg/s | 500 Hz | 13,780x |
| LaserScan | 1480 B | 210 ns | 283 ns | 4.42M msg/s | 40 Hz | 110,500x |
Performance Highlights
Key Findings
- Sub-microsecond latency for messages up to 1.5KB
- Serde integration works flawlessly with complex nested structs
- Graceful scaling with message size (predictable performance)
- Massive headroom for all typical robotics frequencies
Production Readiness
- Real-time control: 75 ns median latency supports 1000Hz+ control loops with over 12,000x headroom
- Sensor fusion: 304-byte Imu messages at 121 ns median, 235 ns p99
- Manipulator control: kilobyte-scale JointCommand payloads at 135 ns median
- Multi-robot systems: multi-million msg/s throughput on a single node
Detailed Results
CmdVel (Motor Control Command)
Use Case: Real-time motor control @ 1000Hz Size: 16 bytes
Median latency: 75 ns
p99 latency: 135 ns
Throughput: 12.14M msg/s
Analysis: Sub-microsecond performance suitable for 1000Hz control loops with over 12,000x headroom.
Imu (Inertial Measurement Unit)
Use Case: Orientation and acceleration @ 500Hz
Size: 304 bytes (size_of; 296 bytes declared)
Median latency: 121 ns
p99 latency: 235 ns
Throughput: 7.46M msg/s
Analysis: Sub-microsecond performance with complex nested arrays and three 9-element (3x3) covariance matrices.
JointCommand (Multi-DOF Control)
Use Case: Manipulator joint control @ 500Hz
Size: 928 bytes (size_of; 1032 bytes declared)
Median latency: 135 ns
p99 latency: 226 ns
Throughput: 6.89M msg/s
Analysis: Sub-microsecond latency for kilobyte-scale command payloads, with room for 500Hz+ manipulator loops.
LaserScan (2D Lidar Data)
Use Case: 2D lidar sensor data @ 10-40Hz Size: 1480 bytes
Median latency: 210 ns
p99 latency: 283 ns
Throughput: 4.42M msg/s
Analysis: Still sub-microsecond at 1.5KB. Easily handles 40Hz lidar updates with enormous headroom.
Comparison with traditional frameworks
Latency Comparison
Measurement Note: HORUS values below are send-only (one-direction). For round-trip (send+receive), approximately double them. The DDS rows are the published reference values
dds_comparison_benchmarkuses when theddsfeature is off (64-byte messages, same-process, sourced to REP 2014 and the Eclipse iceoryx benchmarks) — they are not measured on this machine.
| Framework | Median | p99 |
|---|---|---|
| HORUS Topic (same process, 1:1) | 63 ns | 93 ns |
| HORUS Topic (cross process, 1:1) | 151 ns | 181 ns |
| iceoryx (C++) | ~80 ns | ~200 ns |
| CycloneDDS | ~1,500 ns | ~5,000 ns |
| FastDDS | ~2,000 ns | ~8,000 ns |
| ROS2 Default | ~5,000 ns | ~20,000 ns |
Performance Advantage: against the ROS2 default reference, HORUS is roughly 33x faster cross-process and 79x faster same-process; against FastDDS, roughly 13x and 32x.
Latency by Message Size
Measurement Note: All latencies below are send-only (one-direction publish), measured by
robotics_messages_benchmarkwith an auto-selectedTopicbackend. Sizes aresize_ofat runtime.The ROS 2 column divides that send-only figure by ROS 2's ~5 µs REP 2014 reference, which is an end-to-end number we did not measure. The two sides are therefore not like-for-like and the ratios are indicative, not a benchmark result. For a one-way-to-one-way comparison use the cross-process row above (151 ns), which gives roughly 33x.
| Message Size | Message Type | Median | p99 | Throughput | vs ROS2 default (~5 μs) |
|---|---|---|---|---|---|
| 16 B | CmdVel | 75 ns | 135 ns | 12.14 M msg/s | ~67x faster |
| 304 B | Imu | 121 ns | 235 ns | 7.46 M msg/s | ~41x faster |
| 928 B | JointCommand | 135 ns | 226 ns | 6.89 M msg/s | ~37x faster |
| 1,480 B | LaserScan | 210 ns | 283 ns | 4.42 M msg/s | ~24x faster |
Observation: Latency grows far more slowly than message size — 92x more bytes costs under 3x latency — reflecting efficient serialization and IPC.
Python Performance
The HORUS Python bindings (PyO3) call directly into the Rust shared memory layer, avoiding pickle serialization overhead. Python nodes and Rust nodes communicate through the same shared memory, enabling cross-language interoperability with minimal overhead.
Why Python HORUS is Fast:
- Zero-copy via Rust core: Python bindings call directly into Rust shared memory
- No pickle overhead: Messages use efficient binary serialization
- PyO3 efficiency: Minimal FFI overhead between Python and Rust
No published Python figures. Every number on this page comes from the Rust-side benchmarks; the repository publishes no reference latency or throughput figures for the Python bindings, so this section quotes none. To measure them on your own hardware run
python3 horus_py/benchmarks/bench_python.py(message round-trip, node tick overhead, zero-copy image conversion, multi-node scheduler throughput) orpython3 horus_py/benchmarks/research_bench_python.py --duration 30for sustained runs with a full p50/p95/p99/p99.9 distribution and CSV/JSON output.
TensorPool
HORUS TensorPool provides shared memory tensors optimized for ML/AI workloads. Pre-mapped shared memory means no malloc() or zero-initialization on the hot path.
from horus import TensorPool
import numpy as np
# Create pool
pool = TensorPool(12345) # pool_id
# Allocate tensor (pre-mapped shared memory)
h = pool.alloc([1024, 1024], 'float32')
# Zero-copy NumPy view
arr = h.numpy() # No data copied
# Cross-process sharing via shared memory
descriptor = h.to_descriptor()
Key Advantages:
- Cross-process sharing via shared memory
- Pre-allocated pool — no malloc on hot path
- Refcounted handles — safe concurrent access
- Zero-copy NumPy —
.numpy()returns view
Running Rust Benchmarks
Quick Run
cd horus
cargo run --release -p horus_benchmarks --bin robotics_messages_benchmark
Available Benchmarks
| Binary | Description |
|---|---|
robotics_messages_benchmark | IPC latency with real robotics message types |
all_paths_latency | Topic latency across all backend routes (SpscShm, MpscShm, SpmcShm, PodShm, raw-atomic floor) |
cross_process_benchmark | Cross-process shared memory IPC |
scalability_benchmark | Scaling with producer/consumer thread counts |
determinism_benchmark | Execution determinism and jitter |
research_scalability | Node-count (1-100) and topic-count (1-1,000) scaling |
dds_comparison_benchmark | Comparison with DDS middleware — runs against published reference values by default; add -F dds (and an installed CycloneDDS) for a live comparison |
Run any benchmark with:
cargo run --release -p horus_benchmarks --bin <name>
# JSON output for CI/regression tracking
cargo run --release -p horus_benchmarks --bin <name> -- --json results.json
Criterion micro-benchmarks:
cd horus
cargo bench -p horus_benchmarks
Expected Output
╔══════════════════════════════════════════════════════════════════╗
║ HORUS Robotics Message Types Benchmark ║
╠══════════════════════════════════════════════════════════════════╣
║ Testing real-world robotics message latency (REP 2014) ║
╚══════════════════════════════════════════════════════════════════╝
Platform: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz (12 cores)
Iterations: 50000
Warmup: 5000
╔═════════════════════════════════════════════════════════════════════════════╗
║ Message Type │ Size (bytes) │ Typical Rate │ Use Case ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ CmdVel │ 16 │ 1000+ Hz │ Velocity control commands ║
║ Imu │ 296 │ 500+ Hz │ IMU sensor fusion ║
║ LaserScan │ 1480 │ 10-40 Hz │ 2D lidar navigation ║
║ JointCommand │ 1032 │ 500+ Hz │ Manipulator control ║
╚═════════════════════════════════════════════════════════════════════════════╝
[Topic] Running benchmarks...
─────────────────────────────────────────────────
CmdVel 16 bytes │ median: 75ns │ p99: 135ns │ CV: 1.3146
Imu 304 bytes │ median: 121ns │ p99: 235ns │ CV: 1.1833
LaserScan 1480 bytes │ median: 210ns │ p99: 283ns │ CV: 0.6795
JointCommand 928 bytes │ median: 135ns │ p99: 226ns │ CV: 0.8705
The run also prints a SUMMARY BY MESSAGE TYPE table with throughput and a REAL-TIME SUITABILITY ANALYSIS block. The sizes in the banner table (296 B for Imu, 1032 B for JointCommand) are hard-coded labels in the benchmark header; the per-type result lines print the real std::mem::size_of values (304 B and 928 B).
Use Case Selection
Message Type Guidelines
CmdVel — 16 B, 75 ns median / 135 ns p99
- Motor control @ 1000Hz
- Real-time actuation commands
- Safety-critical control loops
Imu — 304 B, 121 ns median / 235 ns p99
- High-frequency sensor fusion @ 500Hz
- State estimation pipelines
- Orientation tracking
JointCommand — 928 B, 135 ns median / 226 ns p99
- Multi-DOF manipulator control @ 500Hz
- Coordinated joint trajectories
- Arm and gripper command streams
LaserScan — 1480 B, 210 ns median / 283 ns p99
- 2D lidar @ 10-40Hz
- Obstacle detection
- SLAM front-end
Performance Characteristics
Strengths
- Sub-microsecond latency for messages up to 1.5KB
- Consistent performance across message types (low variance)
- Graceful scaling with message size
- Production-ready throughput with large headroom
- Serde integration handles complex nested structs efficiently
Additional Notes
- Complex structs (Imu with three 9-element covariance matrices): 121 ns median — still sub-microsecond
- Large messages (LaserScan at 1480 B): 210 ns median — a 92x size increase over CmdVel costs under 3x latency
Real-World Applications
| Application | Message | Frequency | HORUS median | ROS2 default (REP 2014 ref) | Speedup |
|---|---|---|---|---|---|
| Motor control | CmdVel (16 B) | 1000 Hz | 75 ns | ~5 μs | ~67x |
| Sensor fusion | Imu (304 B) | 500 Hz | 121 ns | ~5 μs | ~41x |
| Manipulator control | JointCommand (928 B) | 500 Hz | 135 ns | ~5 μs | ~37x |
| Lidar SLAM | LaserScan (1480 B) | 10-40 Hz | 210 ns | ~5 μs | ~24x |
Methodology
Benchmark Pattern: Send-Side and One-Way
HORUS measures latency in one direction only — no acknowledgement leg is sent:
Why one-way?
- Comparable: matches the one-way figures published by REP 2014 and the iceoryx benchmarks
- No return-path bias: a round-trip number blends two directions plus the consumer's wake-up cost
- Cross-core: producer and consumer are pinned to separate physical cores, so cache-coherency cost is included
- Conservative for cross-process: the timestamp is written before
send()and read afterrecv(), so serialization, IPC and deserialization are all inside the measurement
What we measure:
- send — producer-side
send()latency via RDTSC with calibrated overhead subtraction (same-process scenarios) - one-way — producer → consumer latency from an RDTSC timestamp carried in
CmdVel.timestamp_ns(cross-process scenarios) - broadcast — one-way latency on latest-value PodShm paths, plus read freshness and skip-ahead counts
What we DON'T measure:
- Round-trip / request-response time — no scenario sends an ACK
- Burst throughput (no backpressure)
- Same-core communication (unrealistic for multi-process IPC)
Test Environment
- Build:
cargo build --releasewith full optimizations - CPU Governor:
performancemode recommended; the figures quoted on this page were captured underpowersave, which inflates cross-process latencies - CPU Affinity: Producer and consumer pinned to separate physical cores (
all_paths_latencyuses main=0, aux=2, pub2=4, cons=6, cons2=8) - Process Isolation: Dedicated topics per benchmark
- Warmup: 5,000 iterations discarded before measurement (10,000 for
determinism_benchmark/dds_comparison_benchmark) - Measurement: RDTSC (cycle-accurate timestamps)
Message Realism
- Actual HORUS library message types
- Serde serialization (production path)
- Realistic field values and sizes
- Complex nested structures (Imu, LaserScan, JointCommand)
Statistical Methodology
- 50,000 iterations per message type (
robotics_messages_benchmark, override with--iterations); 100,000 samples per scenario (all_paths_latency,determinism_benchmark,dds_comparison_benchmark) - Median, p95, p99, p99.9 latency tracking
- Tukey IQR outlier removal (1.5x fence) and bootstrap 95% confidence intervals (10K resamples)
- Variance tracking (min/max ranges, coefficient of variation)
- Multiple message sizes
Measurement Details
RDTSC Calibration:
- Null cost (back-to-back rdtsc): ~36 cycles
- Target on modern x86_64: 20-30 cycles
- Timestamp embedded directly in message payload
Cross-Core Testing:
- Producer and consumer on different CPU cores
- Simulates real multi-process robotics systems
- Includes cache coherency overhead (~60 cycles theoretical minimum)
Scheduler Performance
Scheduler Features
Key capabilities:
- Execution classes:
.compute()moves long-running nodes off the tick path,.async_io()for blocking I/O,.rate()and.order()for per-node pacing and ordering - Fault Tolerance: per-node
FailurePolicy—Fatal,Restart { max_restarts, initial_backoff },Skip { max_failures, cooldown },Ignore - Deterministic mode: opt-in sequential execution with reproducible runs —
Scheduler::new().deterministic(true)(default isfalse) - Safety Monitoring: tick budgets, deadline-miss policies (
Miss), watchdogs and a blackbox recorder
Scalability Performance
research_scalability sweeps 1 → 100 nodes and 1 → 1,000 topics. Node counts above 100 are not benchmarked.
Topic scaling (send + recv latency, Intel Core i7-10750H, powersave governor):
| Topics | p50 | p99 |
|---|---|---|
| 1 | 66 ns | 80 ns |
| 10 | 65 ns | 88 ns |
| 50 | 65 ns | 85 ns |
| 100 | 65 ns | 80 ns |
| 500 | 64 ns | 77 ns |
| 1,000 | 64 ns | 83 ns |
Key Insights:
- Topic lookup is O(1) — latency is flat from 1 to 1,000 topics
- The repository README reports near-linear scheduler scaling to 100 nodes (~14% degradation), measured on an Intel i9-14900K rather than the reference CPU used elsewhere on this page
- Node-scaling timings are dominated by OS scheduling noise under the
powersavegovernor and vary substantially run-to-run; measure on your own hardware with theperformancegovernor before quoting numbers
cargo run --release -p horus_benchmarks --bin research_scalability
Real-Time Performance
Real-Time Node Configuration
HORUS provides real-time facilities for safety-critical applications:
RT Features:
- Tick budget enforcement:
.budget(Duration)per node, reporting aBudgetViolationwhen a tick overruns - Deadline handling:
.deadline(Duration)with.on_miss(Miss::Warn | Miss::Skip | Miss::SafeMode | Miss::Stop), plusScheduler::max_deadline_misses() - Failure policies: per-node
FailurePolicy::Fatal,Restart { max_restarts, initial_backoff },Skip { max_failures, cooldown },Ignore - Watchdog Timers:
.watchdog(Duration)on the scheduler or on individual nodes, to detect hung or crashed nodes
Safety-Critical Configuration
Running with full safety monitoring enabled:
use horus::prelude::*;
let mut scheduler = Scheduler::new()
.tick_rate(1000_u64.hz())
.prefer_rt()
.watchdog(10_u64.ms())
.max_deadline_misses(100);
scheduler
.add(motor_controller)
.order(0)
.rate(1000_u64.hz())
.budget(200_u64.us())
.on_miss(Miss::SafeMode)
.failure_policy(FailurePolicy::Fatal)
.build()?;
No published overhead figures. The repository contains no benchmark that measures watchdog resolution, emergency-stop time, deadline-detection jitter or budget-tracking overhead, so this page does not quote any. For scheduler timing behaviour run
cargo bench -p horus_benchmarks --bench scheduler_jitter, which measures RT tick interval jitter at 500 Hz with and without competing compute nodes.
All-Routes Latency
HORUS automatically selects the backend from the producer/consumer counts and whether the message type is POD. Every topic is shared-memory backed, so the same-thread, cross-thread and cross-process scenarios below can resolve to the same backend — what changes between them is where the two ends run, not the transport. This benchmark measures the latency of each automatically-selected route.
Benchmark Results
Measured with all_paths_latency on an Intel Core i7-10750H (6C/12T, powersave governor), 100K samples per scenario, RDTSC timing with calibrated overhead subtraction. The benchmark defines no target values — these are the scenario names and columns it actually reports.
| Scenario | Type | Backend | p50 | p99 |
|---|---|---|---|---|
| SameThread | send | SpscShm | 20 ns | 26 ns |
| CrossThread-1P1C | send | SpscShm | 63 ns | 93 ns |
| CrossThread-MP1C | send | MpscShm | 226 ns | 429 ns |
| CrossThread-1PMC | send | PodShm | 74 ns | 120 ns |
| CrossThread-MPMC | send | PodShm | 190 ns | 354 ns |
| CrossProc-1P1C | one-way | SpscShm | 151 ns | 181 ns |
| CrossProc-2P1C | one-way | MpscShm | 279 ns | 374 ns |
| CrossProc-1PMC | one-way | SpmcShm | 191 ns | 209 ns |
| CrossProc-PodShm | broadcast | PodShm | 223 ns | 270 ns |
| RawAtomic (hardware floor) | one-way | raw shm | 79 ns | 102 ns |
MP1C/MPMC scenarios are contended (2 producers, or 2 producers and 2 consumers). Framework overhead is the gap to the RawAtomic floor — 72 ns for cross-process 1:1.
Key Achievements
- 20 ns same-thread and 63 ns cross-thread for uncontended 1:1 paths
- ~200 ns for contended multi-producer same-process paths (226 ns MP1C, 190 ns MPMC)
- Sub-200ns for cross-process 1:1 (151 ns)
- ~280 ns for multi-producer cross-process (2 producers → 1 consumer)
- Zero configuration — optimal path selected automatically
- Seamless migration — path upgrades transparently as topology changes
Running the Benchmark
cd horus
cargo build --release -p horus_benchmarks
./target/release/all_paths_latency
Summary
HORUS provides production-grade performance for real robotics applications:
Automatic Path Selection (all_paths_latency, p50):
- 20 ns — Same-thread (SpscShm)
- 63 ns — Cross-thread, 1:1 (SpscShm)
- 151 ns — Cross-process, 1:1 (SpscShm)
- 279 ns — Cross-process, 2 producers → 1 consumer (MpscShm)
- 223 ns — Cross-process broadcast, N:N (PodShm)
- 79 ns — Raw shared-memory atomic (hardware floor)
Robotics Message Types (robotics_messages_benchmark, median):
- 75 ns — CmdVel, 16 B (motor control)
- 121 ns — Imu, 304 B (sensor fusion)
- 135 ns — JointCommand, 928 B (manipulator control)
- 210 ns — LaserScan, 1480 B (2D lidar)
Ready for production deployment in demanding robotics applications requiring real-time performance with complex data types.
Next Steps
- Learn how to maximize performance: Performance Optimization
- Explore message types: Message Types
- See usage examples: Examples
- Get started: Quick Start
Build faster. Debug easier. Deploy with confidence.