HORUS vs ROS2

ROS2 is the industry standard for robotics middleware. It has a massive ecosystem, multi-machine networking, and 15 years of community packages. But its architecture — built around DDS, a distributed data service designed for enterprise networking — adds overhead that many single-machine robots don't need.

HORUS makes the opposite trade-off: it strips out the network layer entirely and uses shared memory for all communication. The result is 100–500x faster IPC, deterministic execution, and a simpler development experience — at the cost of a smaller ecosystem and no built-in multi-machine networking.

This page helps you decide which framework fits your project, or whether to use both.

Already using ROS2? See Coming from ROS2 for a migration guide with side-by-side code examples.

Quick Summary

AspectHORUSROS2
IPC Latency~85 ns (shared memory)~50–100 µs (DDS)
ArchitectureTick-based, deterministicCallback-based, event-driven
Real-TimeAuto-detected from .rate() / .budget()Manual DDS QoS configuration
Config Files1 file (horus.toml)3+ files (package.xml, CMakeLists.txt, launch)
LanguagesRust, PythonC++, Python
EcosystemGrowing (core + package registry)Massive (thousands of packages)
Multi-MachineNot yet (single-machine)Native (DDS network transport)
Visualizationhorus monitor (web + TUI)RViz2, Foxglove, rqt

Performance

Message Latency

HORUS uses shared memory directly — no serialization, no kernel transitions, no DDS middleware layer.

Message TypeSizeHORUSROS2 (FastDDS)Speedup
CmdVel16 B~85 ns~50 µs588x
IMU304 B~400 ns~55 µs138x
Odometry736 B~600 ns~60 µs100x
LaserScan1.5 KB~900 ns~70 µs78x
PointCloud (1K pts)12 KB~12 µs~150 µs13x
PointCloud (10K pts)120 KB~360 µs~800 µs2.2x

The speedup is most dramatic for small, frequent messages (CmdVel, IMU) — exactly the messages that matter for tight control loops above 100 Hz.

Throughput

MetricHORUSROS2
Small messages (16 B)2.7M msg/s~20K msg/s
IMU messages (304 B)1.8M msg/s~18K msg/s

Real-Time Metrics

MetricHORUSROS2
Timing jitter±10 µs±100–500 µs
Per-node overhead<5 µs~50–200 µs per callback
Deadline enforcementBuilt-in (.budget(), .deadline())Manual (rmw QoS)
Emergency stop response<100 µs (Event node)Application-dependent

Architecture

HORUS: Tick-Based, Deterministic

Every tick cycle, the scheduler executes all nodes in a guaranteed order:

Scheduler tick:
  → order 0: SafetyMonitor    (always first)
  → order 1: SensorReader     (always second)
  → order 2: Controller       (always third)
  → order 3: Actuator         (always last)
  → sleep to maintain tick rate
  → repeat

Node 2 (Controller) always sees Node 1's (SensorReader) latest data. The safety monitor always runs before the actuator. Every tick, guaranteed. Two runs of the same code produce the same execution order.

ROS2: Callback-Based, Event-Driven

Callbacks fire when events arrive. Execution order depends on timing, message arrival, and executor implementation:

Executor spin:
  → timer callback fires (sensor)       ← order depends on event timing
  → subscription callback fires (ctrl)  ← may fire before or after sensor
  → timer callback fires (actuator)     ← under load, may be delayed

Under load, callbacks can be delayed or reordered. Two runs of the same code may execute callbacks in different orders. For a motor controller that reads IMU data, this means the IMU reading might arrive before or after the control computation.

For safety-critical systems (surgical robots, industrial cobots, autonomous vehicles), deterministic execution order eliminates an entire class of bugs — race conditions that only manifest under load, at full speed, in production.

Developer Experience

Project Setup

# horus.toml — single source of truth
[package]
name = "my-robot"
version = "0.1.0"

[dependencies]
serde = "1.0"
nalgebra = "0.32"

Node Definition

use horus::prelude::*;

struct MotorController {
    commands: Topic<f32>,
}

impl Node for MotorController {
    fn name(&self) -> &str { "Motor" }
    fn tick(&mut self) {
        if let Some(cmd) = self.commands.recv() {
            set_motor_velocity(cmd);
        }
    }
}

HORUS: 10 lines, no shared pointers, no bind, no QoS depth parameter.

CLI Comparison

TaskHORUSROS2
Create projecthorus new my_robotros2 pkg create my_robot + edit CMakeLists.txt
Buildhorus buildcolcon build
Runhorus runros2 run my_robot my_node
List topicshorus topic listros2 topic list
Echo topichorus topic echo velocityros2 topic echo /velocity
Monitorhorus monitorrqt (separate install)
Add dependencyhorus add serdeEdit package.xml + CMakeLists.txt + rosdep install

Feature Comparison

FeatureHORUSROS2
Pub/Sub TopicsShared memory, 10 auto-selected backendsDDS middleware
Services (RPC)BetaYes
Actions (long-running)BetaYes
Transform FramesTransformFrame (built-in)tf2
Recording/ReplayBuilt-in Record/Replayrosbag2
MonitoringWeb + TUI (built-in)rqt, Foxglove (separate)
Launch SystemYAML launch filesPython/XML/YAML launch
Package Managerhorus registryrosdep, bloom
Deterministic ModeSimClock + dependency graphPartial (use_sim_time)
Safety MonitorBuilt-in (watchdog, graduated degradation)Application-level
Deadline EnforcementBuilt-in (.budget(), .deadline())Manual (rmw QoS)
Multi-MachineNot yetDDS discovery
3D VisualizationNot yetRViz2
Simulationhorus-sim3d (Bevy + native solver)Gazebo, Isaac Sim
Message IDLRust structs with derives.msg/.srv/.action files

Safety & Failure Handling

In ROS2, if a node hangs or misses its deadline, nothing happens — the robot keeps running on its last command. DDS has LIVELINESS and DEADLINE QoS policies, but they only notify you; they don't take corrective action. Building equivalent safety requires writing a custom lifecycle manager and health monitoring node — hundreds of lines that every team writes differently (or skips entirely).

HORUS has this built into the scheduler: a graduated watchdog detects frozen nodes (warn → skip → isolate → safe state), deadline miss policies control what happens when a node overruns its budget (Miss::Warn, Skip, SafeMode, Stop), and fault tolerance handles node crashes with automatic restart, skip, or fatal policies.

See Safety Monitor for the full reference with configuration and code examples.

When to Use Each

Choose HORUS when:

  • Sub-microsecond IPC latency matters (control loops > 100 Hz)
  • Deterministic execution order is required (safety-critical systems)
  • You want a single-file project config
  • Your robot runs on a single machine
  • You prefer Rust's safety guarantees
  • You're starting a new project (no ROS2 migration debt)

Choose ROS2 when:

  • You need multi-machine communication (distributed robots, fleet management)
  • You need RViz2 for 3D visualization
  • You depend on specific ROS2 packages (MoveIt2, Nav2, SLAM Toolbox)
  • Your team already has ROS2 expertise
  • You need the larger ecosystem (drivers, integrations, community support)

Use both:

  • HORUS for real-time control on the robot (sensors, actuators, safety)
  • ROS2 for high-level planning, visualization, fleet management on separate machines
  • A bridge node translates between DDS topics and HORUS topics at the boundary

Migration Path

HORUS and ROS2 can coexist. Common strategies:

  1. Start new subsystems in HORUS — Keep existing ROS2 for high-level planning, add HORUS for real-time control
  2. Bridge approach — Run a bridge node that translates between DDS topics and HORUS topics
  3. Full migration — Replace ROS2 nodes one-by-one with HORUS equivalents

See Coming from ROS2 for detailed migration guidance with side-by-side code examples.

See Also