消除中介軟體抽象層,為新一代機器人帶來前所未有的效能與安全
今天想來分享筆者最近在基於 Zenoh、以 Rust 開發的機器人框架:ros-z!
ROS 2 社群一直強調中介軟體獨立是一大架構優勢,能讓團隊自由選擇 DDS 實作,針對不同需求調整,也保有供應商彈性。可是這份彈性背後,藏著一筆我們心知肚明的代價:效能負擔。
在傳統的 ROS 2 系統裡,每則訊息都得穿過多層抽象:
Your Application
↓
ROS 2 Client Library (rclcpp/rclpy)
↓
RCL (ROS Client Library - C)
↓
RMW (ROS Middleware Interface)
↓
Middleware Implementation (Zenoh/DDS)
↓
Network Transport
每一層都帶來延遲、記憶體分配和潛在的低效率。各程式語言與中介軟體間的切換,還會增加編解碼成本。對於開發高頻控制、自主車輛或大型機器人隊伍的團隊來說,這些毫秒的累積和記憶體消耗,正是一種真實的瓶頸。
若是能完全剔除這些層呢?
ROS-Z 是 ZettaScale 在 垂直優化 的一次大膽嘗試,從零開始、用 Rust 直接基於 Zenoh 協定重寫 ROS 2,完全跳過中介軟體抽象層。
ROS-Z 並不把 Zenoh 當成 ROS 2 RMW 層裡的又一選項,而是干脆 將 Zenoh 立為基礎,直接省略整個中間層:
Your Application
↓
ROS 2 Client Libraries (native or compatible)
↓
ROS-Z / RCL-Z (Rust implementation)
↓
Zenoh Core (Rust)
↓
Network Transport
結果是 少了兩層抽象,直接整合,能靠 Zenoh 原生功能帶來零翻譯負擔。
ROS-Z 提供三種互補方案,打造原生 Zenoh ROS 2 體驗:
ROS-Z 最獨特之處在於採用 Rust,這語言專為機器人系統的挑戰量身打造——高效能、記憶體安全和無懼併發。
傳統 ROS 2 主要靠執行時型別檢查與手動記憶體管理。ROS-Z 利用 Rust 的型別系統,打造 編譯期保障:
// Define a publisher with compile-time type safety
let zpub = node
.create_pub::<Vector3D>("vector")
.with_type_info(TypeInfo::new(
"geometry_msgs::msg::dds_::Vector3_",
TypeHash::from_rihs_string("RIHS01_cc12fe83...").unwrap(),
))
.build()?;
// Publish with zero-copy potential
zpub.publish(&msg)?;
優點:
ROS-Z 建立 trait 制序列化介面,支援多種格式且維持型別安全:
pub trait ZMessage {
type Serdes: ZSerializer + ZDeserializer;
fn serialize(&self) -> Vec<u8> {
Self::Serdes::serialize(self)
}
fn deserialize(input: &[u8]) -> Self {
Self::Serdes::deserialize(input)
}
}
// Automatically implements for CDR-compatible types
impl<T> ZMessage for T
where T: Serialize + Deserialize<'a> + 'a
{
type Serdes = CdrSerdes<T>;
}
帶來的好處包括:
Rust 所有權模型幫助消除傳統機器人系統的併發錯誤:
pub struct ZPub<T: ZMessage> {
sn: AtomicUsize, // Lock-free sequence numbers
inner: zenoh::pubsub::Publisher<'static>,
_lv_token: LivelinessToken, // RAII resource management
_phantom_data: PhantomData<T>,
}
impl<T: ZMessage> ZPub<T> {
pub fn publish(&self, msg: &T) -> Result<()> {
self.inner
.put(msg.serialize())
.attachment(self.new_attchment())
.wait()
}
pub async fn async_publish(&self, msg: &T) -> Result<()> {
self.inner.put(msg.serialize()).await
}
}
重點:
&self
接收保證發佈天生線程安全RCL-Z 演示 Rust C FFI 功力,確保與既有 ROS 2 應用直接兼容:
// rcl-z/build.rs leverages bindgen and CXX
cxx_build::bridge("src/type_support.rs")
.file("src/serde_bridge.cc")
.include("include")
.includes(include_dirs)
.std("c++17")
.compile("serde_bridge");
// Safe Rust implementation exposed as C API
#[no_mangle]
pub extern "C" fn rcl_publisher_init(
publisher: *mut rcl_publisher_t,
node: *const rcl_node_t,
type_support: *const rosidl_message_type_support_t,
topic_name: *const c_char,
options: *const rcl_publisher_options_t,
) -> rcl_ret_t {
rclz_try! {
// Safe Rust implementation
let pub_impl = node_impl.new_pub(type_support, topic_name, options)?;
publisher.assign_impl(pub_impl)?;
Result::Ok(())
}
}
優勢:
ROS-Z 利用 Rust 所有權系統,自動處理資源管理:
pub struct ZNode {
entity: NodeEntity,
session: Arc<Session>, // Reference-counted Zenoh session
lv_token: LivelinessToken, // Auto-cleanup on drop
endpoint_counter: AtomicUsize,
}
impl ZNode {
pub fn create_pub<T: ZMessage>(&self, topic: &str)
-> ZPubBuilder<T>
{
let entity = EndpointEntity {
id: self.endpoint_counter.fetch_add(1, AcqRel),
node: self.entity.clone(),
kind: EntityKind::Publisher,
topic: topic.to_string(),
type_info: None,
qos: Default::default(),
};
ZPubBuilder {
entity,
session: Arc::clone(&self.session),
_phantom_data: PhantomData,
}
}
}
當 ZNode
被丟棄時:
LivelinessToken
自動取消 Zenoh 中的註冊Arc<Session>
引用自動遞減為了解 ROS-Z 在 Zenoh-for-ROS-2 範疇的獨特優勢,我們比較三大實作:
方面 | ROS-Z | rmw_zenoh | zenoh-plugin-ros2dds |
---|---|---|---|
層級 | 替代 RCL + RMW | 實作 RMW 介面 | DDS ↔ Zenoh 橋接 |
語言 | Rust + C FFI | C++ + Zenoh-C 綁定 | Rust + DDS 整合 |
整合方式 | 原生 Zenoh | 透過 RMW 抽象 | DDS 到 Zenoh 協定橋 |
相容性 | RCL API 兼容 (rcl-z) | 完整 RMW 兼容 | 原有 DDS 節點完整支援 |
抽象層數 | 省略 2 層 | 標準 ROS 2 技術棧 | 橋接 + 既有堆疊 |
效能 | 潛力最高 | 高 (部份抽象開銷) | 中等 (橋接 + DDS) |
# Clone the repository
git clone https://github.com/ZettaScaleLabs/ros-z
cd ros-z
# Build with Cargo
cargo build --release
# For RCL-Z (C compatibility layer)
cd rcl-z
export AMENT_PREFIX_PATH=/opt/ros/rolling
cargo build --release -p rcl-z
use ros_z::{Builder, context::ZContextBuilder, ros_msg::Vector3D, entity::{TypeInfo, TypeHash}};
fn main() -> ros_z::Result<()> {
// 建立 Zenoh context
let ctx = ZContextBuilder::default().build()?;
// 建立節點
let node = ctx.create_node("my_node").build()?;
// 建立發布者
let pub = node
.create_pub::<Vector3D>("vector")
.with_type_info(TypeInfo::new(
"geometry_msgs::msg::dds_::Vector3_",
TypeHash::from_rihs_string("RIHS01_cc12fe...").unwrap(),
))
.build()?;
// 發佈循環
loop {
let msg = Vector3D { x: 1.0, y: 2.0, z: 3.0 };
pub.publish(&msg)?;
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
# 預先載入 RCL-Z 函式庫以取代現有 rclcpp 應用
LD_PRELOAD=/path/to/librcl_z.so ros2 run demo_nodes_cpp talker
# 現有程式便跑在原生 Zenoh 堆疊上!
ROS-Z 節點能與任何 rmw_zenoh ROS 2 節點輕鬆通訊:
# 終端機 1:使用 rmw_zenoh 執行標準 ROS 2 訂閱者
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp listener
# 終端機 2:執行 ROS-Z 發佈者
cargo run --example z_pub
ROS-Z 的價值不僅是程式碼,更是一個 概念驗證,證明用現代協定如 Zenoh 垂直整合的可能性:
ROS-Z 是開源且歡迎大家貢獻: