ROSノード間通信の速度について

　ROSをPythonで書いてる人はお帰りください．
　今日はROSのノード間通信の速度について調べてみた結果をご紹介します．

# Nodeletってすごいらしい
　知っての通り，ROSはノード間通信がSocketで実現されているため，物理的に離れたPC間でもroscoreを共有してROSノード同士が互いに通信を行うことができますが，同一PC内でも通信にオーバーヘッドが入ってしまって遅いという難点があります．
　それを解決するのがNodeletという仕組み．１つのプロセス内でNodelet化した複数のROSノードをスレッド化して動作させ，且つそこで動いているROSノード間の通信をshared_ptr渡しにすることで，メッセージのシリアライズ/デシリアライズ処理を無くし，共有メモリによるコピー，いわゆるゼロコピーを行って通信の高速化を行うことができると言われています．そのため，様々な画像処理を行う[image_pipelineなんてパッケージ](https://github.com/ros-perception/image_pipeline)は，そのほぼ全てがNodeletで提供されていたりします．まぁ，とりあえず「[Nodelet Everything](https://www.clearpathrobotics.com/assets/guides/ros/Nodelet%20Everything.html)」という，Clearpath Roboticsの人が書いた記事をみんな読むんだ．

# 評価パッケージ作った
　さて，「速くなる」って言っても，実際どれくらい速くなるのか興味があったので，メッセージのやりとりにかかる時間を計測するパッケージを作成しました．
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">ROSノード間のメッセージパッシング速度を評価するパッケージを作りました． <a href="https://t.co/AsxbANMSSo">https://t.co/AsxbANMSSo</a></p>&mdash; yoneken (@k_yone) <a href="https://twitter.com/k_yone/status/806889600052400130">December 8, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# 使い方
　パッケージをROSワークスペースに[ダウンロード](https://github.com/yoneken/measure_message_passing)してビルドしたら，以下のようにroslaunchを立ち上げることでそれぞれの場合におけるメッセージのやりとりにかかる時間を計測することができます．

```sh:1．１つのノード内でROSメッセージをやり取りする場合
roslaunch measure_message_passing one_node.launch
```

```sh:2．１つのノード内でROSメッセージをshared_ptrでやり取りする場合
roslaunch measure_message_passing one_node_ptr.launch
```

```sh:3．2つのノード間でROSメッセージをやり取りする場合
roslaunch measure_message_passing two_node.launch
```

```sh:4．2つのノード間でROSメッセージをshared_ptrでやり取りする場合
roslaunch measure_message_passing two_node_ptr.launch
```

```sh:5．2つのNodelet間でROSメッセージをやり取りする場合
roslaunch measure_message_passing two_nodelet.launch
```

```sh:6．2つのNodelet間でROSメッセージをshared_ptrでやり取りする場合
roslaunch measure_message_passing two_nodelet_ptr.launch
```

roslaunchを立ち上げてしばらくすると，[1024x768のサイズでbgr8型（OpenCVのCV_8UC3型：符号なし8ビット3チャンネル）の画像データ](https://github.com/yoneken/measure_message_passing/blob/master/src/MeasureTime.cpp#L47)を1000回メッセージパッシングをしたときにかかる時間の平均値・中央値・最悪値（最大値）を出力します．

# 結果
　MSIのゲーミングPC [GS30 2M](https://jp.msi.com/Laptop/GS30-2M-Shadow%EF%BC%8BG-Dock.html)で実際に計測した結果は，

1. one_node (not pointer): Mean: 0.00140118, Median: 0.00139828, Max: 0.00454532
- one_node (pointer): Mean: 5.4087e-06, Median: 3.53647e-06, Max: 0.000275296
- two_node (not_pointer): Mean: 0.00235323, Median: 0.00236079, Max: 0.00825729
- two_node (pointer): Mean: 0.00088381, Median: 0.00087349, Max: 0.0030891
- two_nodelet (not pointer): Mean: 0.000689175, Median: 0.000686938, Max: 0.00333391
- two_nodelet (pointer): Mean: 5.41813e-05, Median: 4.98603e-05, Max: 0.000194368

となり，だいたい 2,6 <<< 5,4 < 1 < 3 くらいの順でメッセージのやり取りにかかる時間は大きくなりました．
　この結果から，やはりNodelet+shared_ptrの効果は絶大だということがわかりました．また，それが難しい場合でも，Nodelet化かメッセージの型をshared_ptrにするかのどちらかをするだけでも，メッセージやりとりの高速化になかなか効果があることがわかりました．

# おまけ
　最初，[subscribeする関数について，普通の型とshared_ptrにした型の2つを同じ名前でオーバーロードで実装していた](https://github.com/yoneken/measure_message_passing/blob/aa6151a4788e9e5a48f0a957486e54851c667778/src/MeasureTime.cpp#L22-L32)のですが，こうすると普通の型で渡そうとしても，なぜか勝手にshared_ptrで渡されてしまい，思ったとおりの結果が出ないということがありました．ROSの機能なのかC++の機能なのかコンパイラの機能なのか，shared_ptrによるゼロコピーが可能な場合は，できるだけ自動でゼロコピーになるようになっているようです．

# 蛇足
　12月17日(土)に秋葉原で古今東西の（自称）ロボット屋を集めて忘年会をします．もしご興味がありましたら，ぜひご参加ください．参加者は毎回流動的なので，初めての方もお気軽にご参加ください．学生歓迎．詳しくは[こちらのページ](https://atnd.org/events/83843)で．
　申込み〆切までに登録しておいてもらえれば，参加者枠を広げられると思います．

# 追記（20/07/27）
　やり取りしているメッセージのサイズについて追記しました。