intdash_ros2bridgeの伝送遅延をrosbridge_serverと比較する

f:id:apt-k-ueno:20211215172641j:plain

aptpod Advent Calendar 2021の17日目を担当するProtocol/Robotics Teamの酒井 (@neko_suki) です。

弊社では、intdash_ros2bridgeというROS2上で「任意のトピック、サービス、アクションのC++実装によるブリッジ」を実現するソフトウェアの開発を進めています。ブリッジとは、ROS2空間の内部のデータを、インターネット経由で伝送できるメッセージ形式に橋渡しをする処理のことを指します。

このintdash_ros2bridgeによって離れた2つのROS2空間をつなぐことが可能になります。このintdash_ros2bridgeによって、ROS1*1と同様にROS2でも遠隔制御やモニタリングなどが実現できます。

過去に書いた紹介記事では、intdash_ros2bridgeで実現できることや、技術的な実現方法をご紹介してきました。

tech.aptpod.co.jp

tech.aptpod.co.jp

今回の記事では、intdash_ros2bridgeと、 ROSメッセージのブリッジによく使われる rosbridge_server のメッセージ伝送の遅延を比較しましたので、その結果をご紹介します。

遅延値による性能の比較

遅延の計測には、performance_test (release-foxy-20210519) というツールを使用しました。

performance_testは、テスト用のトピックのpublishとsubscribeを行います。配列、点群などのテスト用のトピックが用意されています。

performance_testは、メッセージをpublishするときにメッセージにタイムスタンプを含めます。このタイムスタンプと、メッセージがsubscriberで受信されたときのタイムスタンプを比較することで遅延を計算することが出来ます。

しかし、タイムスタンプは、ClockCycles 関数で取得されたマシン固有のクロック値であるため、今回のようにメッセージを別のPCで受信する場合は、この方法で遅延を求めることはできません。

そこで、以下の図に示すように、PC1からインターネットを経由した先にあるPC2で受信したときの遅延ではなく、ループバックさせてメッセージがPC1に戻ってくるまでの遅延を計測します。

遅延値は、以下の図のようにperformance_test(publisher)がメッセージをpublishするときに付けたタイムスタンプと、performance_test(subscriber)がメッセージを受信した時のタイムスタンプの差分を使用します。

f:id:aptpod_tech-writer:20211215093310p:plain
遅延の計算方法

戻ってきたメッセージを受信したPC1で、performance_test(subscribe)は、トピックのリネームを行います。これにより、performance_test(publisher)がpublishしたばかりのトピックをsubscribeすることによる無限ループを回避しています。

参考までに、performance_testは以下のように実行します。

performance_test(publisher)

$ ./install/performance_test/lib/performance_test/perf_test -c ROS2 -l log -t Array1k -r 1 --max_runtime 120 --num_sub_threads 0

performance_test(subscriber)

$ ./install/performance_test/lib/performance_test/perf_test -c ROS2 -l log -t Array1k -r 1 --max_runtime 120 --num_pub_threads 0 /Array1k:=/Array1k_test

計測の構成

intdash_ros2bridge

intdash_ros2bridgeを使用した場合は以下のような構成になります。

f:id:aptpod_tech-writer:20211215094538p:plain
intdash_ros2bridge使用時の構成

計測側のPC1では、performance_test(publisher)、performance_test(subscriber)を動作させます。

PC1のintdash_ros2bridgeは、performance_test(publisher)がpublishしたトピックを受信し(①)、受信したメッセージをFIFOに書き込みます(②)。FIFOに書かれたメッセージは弊社プロダクトのintdash Edge Agentによって読み込まれ、インターネット経由で弊社プロダクトのintdash Serverを経由し、PC2のintdash Edge Agentに渡されます(③)。

PC2のintdash Edge Agentは、受信したメッセージをFIFOに書き込みます(④)。PC2のintdash_ros2bridgeはメッセージをFIFOから読み込みpublishします(⑤)。publishされたメッセージは、intdash_ros2bridge自身によって受信されます。そして、PC2のintdash_ros2bridgeはFIFOにそのメッセージを書き込みます(⑥)。

PC1の時と同様に、メッセージはintdash Edge Agent、intdash Serverを経由して、PC1のintdash Edge Agentに届きます(⑦)。PC1のintdash Edge AgentはメッセージをFIFOに書き込みます(⑧)。PC1のintdash_ros2bridgeは、ループバックしてきたメッセージをFIFOから読み込み、トピックをリネームしてpublishします(⑨)。

performance_test(subscriber) はリネームされたメッセージを受信します。最後にperformance_test(subscriber)は、受信した時点のタイムスタンプを取得し、遅延値を計算します。

rosbridge_server

rosbridge_serverを使用する場合は以下のような構成になります。

f:id:aptpod_tech-writer:20211215102554p:plain
rosbridge_server使用時の構成

intdash_ros2bridgeを使う場合との大きな違いは、rosbridge_serverとintdash Edge Agentが直接メッセージをやり取りする手段を持たない点です。 これを解決するために、rosbridge connector というc++で書かれたソフトウェアを用意します。rosbridge connectorはWebSocketを使ってrosbridge_serverとメッセージのやり取りを行います。また、rosbridge connectorはFIFOへの読み書きを行うことで、intdash Edge Agentとメッセージのやり取りを行います。

PC1で、トピックをsubscribeしたrosbridge_server(①)はメッセージをJSONに変換し、WebSocket経由でrosbridge connectorに渡します(②)。rosbridge connector は渡されたメッセージをFIFOに書き込みます(③)。FIFOに書かれたメッセージがintdash Edge Agent を経由し、PC2に届く仕組みはintdash_ros2bridgeと同様です(④)。

PC2でrosbridge connectorはFIFOからメッセージを読み込みます(⑤)。rosbridge connectorは読み込んだメッセージをWebSocket経由でrosbridge serverに渡します(⑥)。rosbridge_serverは受け取ったトピックをpublishします。そしてrosbridge_server は自身がpublishしたメッセージを受信します(⑦)。そして、このメッセージはrosbridge connector、intdash Edge Agentなどを経由しPC1のrosbridge connectorに届きます(⑧、⑨、⑩、⑪)。

rosbridge connector はPC1に戻ってきたメッセージのトピックをリネームします。ここでは文字列の状態で、"topic":"topic_name" となっている部分を、"topic":"renamed_topic" に書き換えるようにしています。 rosbridge connectorはリネームしたメッセージをrosbridge_serverに渡します(⑫)。そして、rosbridge_serverはこのメッセージをpublishします(⑬)。

performance_test(subscriber) はリネームされたメッセージを受信します。最後にperformance_test(subscriber)は、受信した時点のタイムスタンプを取得し、遅延値を計算します。

計測に使用したソフトウェア

rosbridge_server

rosbridge_server は 実験時点の最新コミット がそのままでは使えなかったので、以下の2点の修正を加えて使用しています。

  • performance_test のトピックを扱えるように修正
  • qosをbest_effortに変更 *2

計測に使用するフォーマット

遅延を計測するためのフォーマットは、intdash_ros2bridgeはCDR*3、rosbridge_serverはJSONを使用します。

rosbridge_serverのフォーマットは、性能の観点ではサイズが一番小さくなると考えられるCBOR*4を使うのが望ましいと考えられます。しかしリポジトリからcloneした状態では動作しなかったので今回はJSONとの比較をしました。

本家の方のバグが直ったらCBORとの比較も行いたいと思います。

計測に使用するトピック

今回は以下の3種類のトピックで遅延時間を計測しました。

  • Struct256
  • Array1k
  • PointCloud512k

それぞれのトピックは、int64のタイムスタンプと、uint64型のID、そのトピック独自の構造を保持しています。 タイムスタンプは、ClockCycles 関数で取得されたマシン固有のクロック値です。

計測では、トピックを発行する頻度を1 Hz、10 Hz、100 Hz、1000 Hzの4通りとしました。ただし、サイズ上計測できた上限までとなっています。

また、トピックのQoSはベストエフォートに設定しています。

そして、計測は120秒間実行しました。

Struct256

Struct256 は、256のバイトデータを含んだ構造体です

16個の1バイトの要素を持ったStruct16 という構造体を16個持っています。

Struct16 struct160
Struct16 struct161
Struct16 struct162
Struct16 struct163
Struct16 struct164
Struct16 struct165
Struct16 struct166
Struct16 struct167
Struct16 struct168
Struct16 struct169
Struct16 struct16a
Struct16 struct16b
Struct16 struct16c
Struct16 struct16d
Struct16 struct16e
Struct16 struct16f
int64 time
uint64 id
Array1k

Array1k は1024個の要素を持つバイト列を含む情報です。

byte[1024] array
int64 time
uint64 id
PointCloud512k

PointCloud512k は524288個の点群データを持つデータ構造です。

点群部分の中身は、PointCloud2 と同様です。

std_msgs/Header header
uint32 height
uint32 width
bool    is_bigendian
uint32  point_step   # Length of a point in bytes
uint32  row_step     # Length of a row in bytes
uint8[524288] data         # Actual point data, size is (row_step*height)
bool is_dense        # True if there are no invalid points
int64 time
uint64 id

計測に使用したマシン

PC1には、Intel社のNUCというミニPCを使っています。CPUは、Core(TM) i7-8559U CPU、メモリは8GB積んでいます。 PC2の環境は、AWS EC2の c6g.xlarge というARMのインスタンスを使用しました。

計測はDocker上で行いました。

計測結果

performance_test に含まれる解析ツールから遅延の最小値、平均値、最大値が得られるため、それらを比較します。

これらの値は、PC1→PC2→PC1という往復の遅延です。なので、絶対的にXX msec速いという比較ではなく、相対的にどちらが速いかが比較できます。

Struct256

平均値を見ると、トピックのpublish頻度によらず、intdash_ros2bridgeは47.5~49.2msec、rosbridge_serverは90.2~113.3msec となっています。 ここから、Struct256 の場合、intdash_ros2bridgeの方が相対的に速いことがわかります。

Hz intdash_ros2bridge rosbridge_server
min (msec) mean (msec) max (msec) min (msec) mean (msec) max (msec)
1 40.2 49.2 61.9 74.0 96.2 201.1
10 39.3 48.8 59.8 88.5 113.3 134.9
100 27.7 47.5 70.9 63.1 90.2 132.2
1000 39.5 48.9 68.3 71.5 102.3 146.3

Array1k

平均値を見ると、intdash_ros2bridgeは43.9msec~53.1msec、rosbridge_serverは96.3~111.1msec となっています。したがって、Array1kも同様に、intdash_ros2bridgeの方が相対的に速いことがわかります。

また、rosbridge_serverは1000 Hz まで頻度を上げると、計測が出来ませんでした。

Hz intdash_ros2bridge rosbridge_server
min (msec) mean (msec) max (msec) min (msec) mean (msec) max (msec)
1 34.1 44.6 65.6 62.35 96.3 146.6
10 43.1 53.1 63.2 89.5 111.1 130.6
100 37.6 43.9 60.9 64.2 98.2 147.1
1000 39.8 50.8 75.2 計測不能 計測不能 計測不能

PointCloud512k

平均値を見ると、intdash_ros2bridgeは181.6~189.1msec、rosbridge_serverは 389.3msecとなっています。 PointCloud512kの場合も、intdash_ros2bridgeの方が相対的に速いことがわかりました。

Hz intdash_ros2bridge rosbridge_server
min (msec) mean (msec) max (msec) min (msec) mean (msec) max (msec)
1 178.1 181.6 394.2 340 389.3 621.3
10 177.3 189.1 204.9 計測不能 計測不能 計測不能

これらの結果から、

intdash_ros2bridgeの方が、rosbridge_serverよりも低遅延でのROS2のブリッジを実現できる

と言えそうです。

考察

ペイロードのサイズ確認

ブリッジされたトピックのサイズを確認します。

トピック intdash_ros2bridge(CDR) rosbridge_server(JSON)
Array1k 1.06KByte 10.10KByte
Struct256 583Byte 5.89 KByte
PointCloud512k 512.27 KByte 683.37 KByte

ここから、やはりバイナリ形式であるCDR形式の方が、効率的なデータ伝送が出来ている可能性がありそうです。

ただし、PointCloud512kだけは、intdash_ros2bridgeとrosbridge_serverの出力するトピックのペイロードサイズの差が小さい結果になりました。 これは、rosbridge_serverがuint8の配列をbase64エンコーディングしているのが理由です。

rosbridge connector 処理時間

rosbridge_serverとintdash Edge Agentの間のrosbridge connectorは実験用に開発したものです。これがボトルネックになっていないことも確認しておきます。

rosbridge connectorの処理は、WebSocket経由でメッセージを受信してFIFOに書き込む処理と、FIFOからメッセージを読み込んでWebSocketに渡す処理が行われています。

実際の実行時間は以下のようになります。

トピックの種類 WebSocket受信からFIFOに書き込む前まで FIFOから読み込んでからWebSocketに渡すまで
Array1K 0.2msec 3msec
Struct256 0.1msec 2msec
PointCloud512k 5msec 55msec

ここから、大きなボトルネックにはなっていないと言えそうです。

まとめ

本記事では、ROS2メッセージの遠隔リアルタイムデータ伝送を実現する新プロダクト「intdash_ros2bridge」と、rosbridge_serverのメッセージ伝送時の遅延時間比較を行いました。

実験の結果から

intdash_ros2bridgeの方が、rosbridge_serverよりも低遅延でのROS2のブリッジを実現できる

と言えそうです。

弊社では現在のintdash_ros2bridgeの利用拡大やROSコミュニティへの貢献を視野に入れたOSS化に向けた計画も進めています。

プロダクト開発の進捗やOSS化の進捗がありましたらまた続報をお届けできればと思います。

最後までご覧いただきありがとうございました。

*1:弊社では、 ROSの任意トピックをC++ノードでPublish/Subscribeする方法 - aptpod Tech Blog に書かれている技術を用いた intdash_bridge というROS1で離れた2つのROS1空間をつなぐプロダクトを提供しています。

*2:その後対応されたようです。https://github.com/RobotWebTools/rosbridge_suite/commits/ros2

*3:rosbag2 でも使われている バイナリ形式に近いフォーマット。詳細は ROS2で任意のメッセージをC++ノードでブリッジする方法 - aptpod Tech Blog で紹介しています。

*4:RFC 8949 Concise Binary Object Representation っで規定されている格納効率の良いバイナリフォーマット