ROS2でプロセス間通信をもっと軽量にしたい！DDS・Zenoh・共有メモリ・Compositionを比較

ROS2を使っていると、「ノード間の通信、もうちょっと軽くならないかな……」と思う場面、多くないですか？
今回は、ROS2の通信を高速・軽量化するために試した Zenoh, 共有メモリ通信, Composition の3つの手法をざっくり紹介しつつ、実際にやってみた結果を共有します。

### 検証環境

| 項目                     | 内容                                     |
|--------------------------|------------------------------------------|
| ROS2 バージョン         | jazzy                                   |
| OS                | Ubuntu 20.04 上の Ubuntu 22.04 Docker コンテナ |
| rmw_zenoh_cpp            | 0.2.3                                    |
| rmw_dds_common           | 3.1.0                                    |
| rmw_cyclonedds_cpp       | 2.2.2                                    |
| rmw_fastrtps_shared_cpp  | 8.4.1                                    |

# Zenoh：軽量・分散・リアルタイムな通信プロトコル

[Zenoh](https://zenoh.io) は、Pub/Sub・Query（リクエスト/リプライ）・データ保存までを1つでこなす通信ミドルウェア。IoTやロボットなどのリアルタイム・分散通信にピッタリの設計になっています。

### 特徴

| 特徴 | 説明 |
|------|------|
| Pub/Sub + Query対応 | 必要なデータだけオンデマンド取得も可能 |
| 超軽量・低レイテンシ | エッジ環境でもサクサク動く |
| ピアツーピア通信 | ブローカー不要でスケーラブル |
| ROS2対応 | Zenoh-Bridgeやrmw_zenohで統合可能 |

### 導入方法（2パターン）

#### ① Zenoh Bridge for DDS

既存のROS2ノード（DDS）を変更せず、Bridge経由でZenoh通信が可能。段階的な導入に向いています。

```bash
cargo install zenohd  # Rustが必要
cargo install zenoh-plugin-dds # binが無いと言われるので、buildが必要
sudo apt install build-essential clang

git clone https://github.com/eclipse-zenoh/zenoh.git
git clone https://github.com/eclipse-zenoh/zenoh-plugin-dds.git
cd zenoh
cargo build --release
cd ../zenoh-plugin-dds
cargo build --release
```

ただし、docker container環境では立ち上げできなかったので、深追いしてない。

#### ② rmw_zenoh

DDSを完全にZenohに置き換える方法。軽量＆高速が必要なローカル環境、またはマルチキャスト不可なネットワークで有効。

以下apt installだと、zenoh-c　のbuildが必要になり、buildしても動かなかったので、sourceからbuildした。

```bash
sudo apt update
sudo apt install ros-jazzy-rmw-zenoh-cpp
```

```
$ ros2 run demo_nodes_cpp talker
/opt/ros/jazzy/lib/demo_nodes_cpp/talker: symbol lookup error: /opt/ros/jazzy/lib/librmw_zenoh_cpp.so: undefined symbol: zc_reply_keyexpr_default
[ros2run]: Process exited with failure 127
```

#### sourceからbuild

```
mkdir ~/ws_rmw_zenoh/src -p && cd ~/ws_rmw_zenoh/src
git clone https://github.com/ros2/rmw_zenoh.git -b jazzy
cd ~/ws_rmw_zenoh
rosdep install --from-paths src --ignore-src --rosdistro jazzy -y
source /opt/ros/jazzy/setup.bash
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release

source ~/ws_rmw_zenoh/install/setup.bash
```

#### 動かし方

zenoh router起動
```
# terminal 1
ros2 run rmw_zenoh_cpp rmw_zenohd
```

```
# terminal 2
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp talker
```

```
# terminal 3
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp listener
```

# 共有メモリ通信：ゼロコピーの爆速IPC

共有メモリを使うと、複数のプロセスが同じメモリ領域を参照できるため、シリアライズ・コピー不要の超高速通信が可能になります。

### 実装例（SysV API）

ROS2ではないが、Linuxの実装を試してみると、以下の感じ。

https://github.com/imoted/shared_memory_communication/tree/master

### DDSごとの対応状況

| DDS実装 | 共有メモリ対応 | 備考 |
|---------|----------------|------|
| Fast DDS | ✅ 独自SHM対応（XML設定） |
| Cyclone DDS | ✅ Iceoryx連携（RouDi必要） |
| RTI Connext | 一部対応（商用、Iceoryx非連携） |

---

#### fastDDSでの設定

```
<?xml version="1.0" encoding="UTF-8" ?>
<dds>
  <profiles>
    <!-- SHM トランスポート定義 -->
    <transport_descriptors>
      <transport_descriptor>
        <transport_id>shm_transport</transport_id>
        <type>SHM</type>
        <segment_size>2536870912</segment_size> 
      </transport_descriptor>
    </transport_descriptors>
    <!-- Participant プロファイル -->
    <participant profile_name="shm_profile" is_default_profile="true">
      <rtps>
        <userTransports>
          <transport_id>shm_transport</transport_id>
        </userTransports>
      </rtps>
    </participant>
  </profiles>
</dds>

```

```
export FASTRTPS_DEFAULT_PROFILES_FILE=/path/fastdds_shm_profile.xml
```

#### cycloneDDSでの設定 / 立ち上げ

```
<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS xmlns="https://cdds.io/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://cdds.io/config https://raw.githubusercontent.com/eclipse-cyclonedds/cyclonedds/iceoryx/etc/cyclonedds.xsd">
    <Domain id="any">
        <SharedMemory>
            <Enable>true</Enable>
            <LogLevel>info</LogLevel>
        </SharedMemory>
    </Domain>
</CycloneDDS>
```

```
export CYCLONEDDS_URI=/path/cyclonedds.xml
```

* iceoryx roudiの事前の立ち上げが必要
```
iox-roudi
```

* talker

``` 
export CYCLONEDDS_URI=file://$PWD/cyclonedds.xml
. ~/ros2_ws/install/local_setup.bash
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp talker
```

* listener
```
export CYCLONEDDS_URI=file://$PWD/cyclonedds.xml
. ~/ros2_ws/install/local_setup.bash
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp listener
```

おそらくcyclone DDSでの共有メモリは、さらに設定をしないと、大容量通信で使えず今回検証では使用していない。追って要調整。

# Composition：ノードを1プロセスにまとめて効率化！

Compositionを使えば、複数のノードを同じプロセス内で動かせるため、起動時間短縮・通信最適化・メモリ削減が期待できます。

- 起動高速化（プロセス1つ）
- DDSのloan機能でゼロコピー通信も可能
- シンプルなノード統合が可能


# ベンチマークしてみた！

## 環境

* CPU 
  * 11th Gen Intel(R) Core(TM) i7-1195G7 @ 2.90GHz
* mem
  * 32GB

## 検証データ

https://github.com/imoted/ROS2_pub_sub_test

### 100MBデータをPub/Sub (1Hz)

| RMW実装 | CPU使用率 |  備考 |
|---------|------------|--------|
| Fast DDS | pub 4〜10% / sub 3〜6% |  |
| CycloneDDS | pub 10%〜28% / sub  10〜20% |  |
| Zenoh | pub 10〜20% / sub 7〜12% | 200MB超でSubscribeできなくなった |
| Fast DDS + SHM | pub 1〜6% / sub 3〜6% | |
| Composition | pub/sub 3〜5% |  |

### 1GBデータをPub/Sub（1Hz）

| RMW実装 | CPU | メモリ | 
|------|-----|--------|
| Fast DDS | pub 40〜50% / sub 80% | pub  35% / sub   35% | 
| CycloneDDS | 通信不可 | - | 
| Zenoh | 通信不可 | - | 
| Fast DDS + SHM | pub 10〜30% / sub 50% | pub  11% / sub    17% |
| Composition | pub/sub 30〜40% | pub/sub    14% |

### 100kHzで「Hello world」Pub/Subしたとき

| RMW実装 | CPU使用率 |
|------|-------------|
| Fast DDS | pub/sub 60% |
| CycloneDDS | pub/sub 50% |
| Zenoh | pub 130% / sub 100%（落ちた） |
| Fast DDS + SHM | pub/sub 60% |

## Rviz2での表示可否

| 実装 | Rviz表示可？ |
|------|---------------|
| Fast DDS | ✅ OK |
| CycloneDDS | ✅ OK |
| Zenoh | ✅ OK |
| Fast DDS + SHM | ✅ OK |

---

## まとめ

結論としては、

```
Composition ＞ Fast DDS + SHM ＞ Fast DDS ≒ CycloneDDS ＞＞ Zenoh
```

* Zenoh は現時点では大容量/高速通信に弱く、発展途上な印象でした。
* 共有メモリはシンプル＆高速だけど、やはり普通にcompositionにしたほうが早いので、使いどころが難しい
* 軽量・高速通信が求められるロボットやIoT開発では、DDSだけでなくZenohや共有メモリの活用も重要です。  
この記事が少しでもROS2のチューニングの参考になれば嬉しいです！