Skip to content

ROS 2 Communication Comparison

This page defines the small comparison track used to calibrate Librux Event communication numbers against ROS 2 communication paths on the same hosts.

The goal is not to build a broad communication benchmark suite. Use this track to answer one question first. What latency floor do the host, network, kernel, ROS 2 executor, rclcpp, and RMW layer allow before interpreting Librux cross-host results?

Do not mix release smoke measurements into the comparison tables. Release smoke runs use small counts to validate installability and path correctness. The comparison data below comes from controlled matrix runs on the same hosts and is the only data on this page that should be used for ROS 2 interpretation.

Scope

Run the comparison in this order.

  1. ROS 2 path with the performance_test ROS 2 plugin and rmw_cyclonedds_cpp.
  2. ROS 2 path with the same plugin and rmw_fastrtps_cpp.
  3. Librux Event path with the existing Librux communication matrix.

For cross-host latency, prefer round-trip mode and interpret one-way latency as roughly half of the reported round-trip value. One-way timestamps across hosts are useful for Librux debugging, but they depend on clock synchronization.

The stock performance_test timestamp path uses std::chrono::steady_clock. That clock is monotonic per host and is not synchronized by PTP or NTP, so stock performance_test one-way latency is not valid across hosts. Use round-trip mode for strict cross-host numbers. A local system_clock rebuild can be used as a diagnostic variant, but sub-50 us one-way values should be treated as clock-offset-limited unless the clock discipline is independently verified. When both hosts have accepted hardware PTP or hardware-backed Librux logical clock evidence, one-way cross-host rows can be reported as an accepted track. Otherwise label one-way ROS 2 rows as diagnostic and keep RTT as the strict comparison.

Test Host Assumptions

Use two equivalent Ubuntu hosts on the same network segment when comparing cross-host behavior. For Ubuntu 24.04, the matching ROS 2 deb-package track is ROS 2 Jazzy.

Setup

Install ROS 2 Jazzy and the RMW packages on both hosts, then make sure performance_test is available. If your environment does not provide a package for performance_test, build it from source with the same compiler and ROS 2 environment on both hosts.

Manual setup sketch.

sudo apt update
sudo apt install -y ros-jazzy-rmw-cyclonedds-cpp ros-jazzy-rmw-fastrtps-cpp
source /opt/ros/jazzy/setup.bash

Run

Run the same payload, rate, and subscriber matrix for each RMW implementation. Use round-trip mode for strict cross-host interpretation, or document any one-way clock discipline separately.

Example environment selection.

export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
source /opt/ros/jazzy/setup.bash

Then run the publisher/subscriber pair with the chosen performance_test options on the two hosts. Repeat with this command.

export RMW_IMPLEMENTATION=rmw_fastrtps_cpp

For the fanout track, use subscriber counts 1,2,4,8,16 and payloads 64 B, 1 KiB, and 4 KiB at 1000 Hz.

For the extended comparison track, use staged profiles rather than a single large run.

Stage Payloads Subscriber counts Purpose
Smoke 64 B, 1 KiB, 4 KiB 1,2,4,8,16 verify setup and artifact capture
Coarse 8 B-1 MiB subset 1-256 identify payload and fanout slope changes
Full 8 B,64 B,256 B,1 KiB,4 KiB,16 KiB,64 KiB,256 KiB,1 MiB 1,2,4,8,16,32,64,128,256 official comparison candidate
Stress 8 B Librux / small ROS 2 array message 512,1024 limit finding outside the official comparison envelope

Before the full Librux leg, run tools/qa/run_comms_matrix.py --plan-only with the same matrix profile and review the generated SHM resource plan. Increase /dev/shm, reduce queue depth, or split the large-payload fanout cases if the plan reports burst pressure warnings.

The ROS 2 runner has the same profile names. Use full for accepted comparison tables and stress only for high-fanout limit finding.

python3 tools/qa/run_ros2_performance_baseline.py \
  --host-a <host-a> --host-b <host-b> \
  --matrix-profile full \
  --fanout --plan-only

performance_test uses its own supported message names, so the ROS 2 full profile maps the payload axis to array messages such as Array1k, Array4k, Array16k, Array60k, and Array1m. Keep the raw manifest with the report so the exact message set is visible.

For shared Librux / ROS 2 planning, prefer the transport matrix runner before executing either stack.

python3 tools/qa/run_transport_matrix.py \
  --profile tools/qa/matrix_profiles/transport_comparison_smoke.yaml

The shared runner expands the same matrix into Librux and ROS 2 cases, maps ROS 2 payload sizes to the closest performance_test array message, and writes the supported and unsupported case list to JSON, CSV, and Markdown. Unsupported rows are part of the artifact. They show which comparison adapters still need to be implemented before a larger matrix can be called complete.

Use transport_comparison_full.yaml for official-candidate planning and transport_comparison_stress.yaml only for limit finding outside the accepted comparison envelope.

Current implemented shared adapters.

Runtime Surface Supported axis
Librux Event fanout one topic, N subscribers
Librux Event multi-topic N topics, one subscriber endpoint
Librux Control procedure one synchronous request/response connection
Librux Timed Exchange one real-time round-trip connection
ROS 2 CycloneDDS / FastDDS Event fanout one topic, N subscribers through performance_test

Current planned adapters.

Surface Missing adapter
ROS 2 Event multi-topic multiple performance_test topic launcher
ROS 2 procedure ROS 2 service latency runner
ROS 2 operation ROS 2 action latency runner
ROS 2 timed-control reference explicit periodic service-loop reference
Librux Control connection count concurrent connection runner

The official comparison envelope stops at N=256. Larger fanout runs are useful for capacity planning, but the observed latency and loss characteristics are dominated by scheduling, local fanout dispatch, and resource limits rather than the low-N communication latency floor.

Reference Results

The comparison reference uses 64 B, 1 KiB, and 4 KiB payloads with subscriber fanout N=1,2,4,8,16 at 1000 Hz.

Same-host 1:N summary.

Stack Delivery Mean latency range
Librux Event lossless 23.413-39.963 us
ROS 2 + CycloneDDS lossless 23.227-78.338 us
ROS 2 + FastDDS loss observed in every fanout row 25.588-80.485 us

Because this is a payload-by-subscriber-count matrix, use the range table only as a quick overview. The matrix below preserves the subscriber-count axis.

Same-host mean latency matrix, in microseconds.

Payload Stack N=1 N=2 N=4 N=8 N=16
64 B Librux Event 23.808 27.715 29.772 28.627 37.935
64 B ROS 2 + CycloneDDS 32.580 32.288 35.087 50.491 45.742
64 B ROS 2 + FastDDS 31.486 25.588 31.159 48.493 80.485
1 KiB Librux Event 27.984 26.734 26.828 39.963 29.071
1 KiB ROS 2 + CycloneDDS 23.227 24.555 43.564 48.412 68.055
1 KiB ROS 2 + FastDDS 32.733 44.436 46.545 46.595 60.788
4 KiB Librux Event 23.880 23.413 24.373 24.959 32.066
4 KiB ROS 2 + CycloneDDS 32.027 33.042 36.425 41.923 78.338
4 KiB ROS 2 + FastDDS 31.364 35.947 43.651 48.056 65.958

The same matrix is shown below as payload-split line charts.

Same-host 1:N mean latency by payload and subscriber count

Cross-host 1:N summary.

Stack Delivery Mean latency range
Librux Event lossless 116.631-175.347 us
ROS 2 + CycloneDDS system-clock diagnostic lossless, clock-offset limited -39.156-80.644 us
ROS 2 + FastDDS system-clock diagnostic loss observed in every fanout row 39.836-104.686 us

The negative CycloneDDS diagnostic rows are a measurement warning, not negative network latency. They show that the one-way latency is below the reliable clock floor of that diagnostic setup.

These cross-host one-way diagnostic rows are not release acceptance numbers. For strict cross-host comparison, prefer RTT mode or a separately accepted production time-sync track.

Cross-host diagnostic mean latency matrix, in microseconds.

Payload Stack N=1 N=2 N=4 N=8 N=16
64 B Librux Event 127.313 131.708 122.870 136.560 122.567
64 B ROS 2 + CycloneDDS diagnostic 1.737 -34.978 -39.156 -24.630 7.389
64 B ROS 2 + FastDDS diagnostic 39.836 48.734 45.191 56.630 51.447
1 KiB Librux Event 116.631 124.637 146.906 159.254 164.075
1 KiB ROS 2 + CycloneDDS diagnostic -0.478 4.048 26.337 39.021 56.819
1 KiB ROS 2 + FastDDS diagnostic 49.388 61.028 43.233 60.637 75.295
4 KiB Librux Event 154.497 165.032 163.762 148.765 175.347
4 KiB ROS 2 + CycloneDDS diagnostic 30.533 46.425 41.907 60.206 80.644
4 KiB ROS 2 + FastDDS diagnostic 65.760 73.000 81.086 65.490 104.686

The same diagnostic matrix is shown below as payload-split line charts.

Cross-host 1:N diagnostic mean latency by payload and subscriber count

Interpretation

Compare the resulting numbers in three bands.

Band Meaning
ROS 2 + Cyclone DDS ROS 2 framework overhead on top of rmw_cyclonedds_cpp
ROS 2 + Fast DDS ROS 2 framework overhead on top of rmw_fastrtps_cpp
Librux Librux runtime, federation, queueing, and transport behavior

If both ROS 2 RMW paths show high cross-host round-trip on the same hosts, tune the test hosts first. Check CPU governor, C-states, interrupt moderation, NIC offloads, switch path, and background load. If ROS 2 is low but Librux remains high, keep profiling the Librux federation/data-plane path.

References.