はじめまして。プラットフォーム事業部の足立(@moguriso)です。昨年中は、Cloud IoT OS開発チームに所属しながら、あまりCloud IoT OSとは関係のないIoT Gatewayデバイス(BL-02)の開発に関わって台湾*1に行ったり、最近はデータ収集用に構築されたROS環境にnodeを追加するお仕事をやっています。
はじめに
本稿は、当社が10/24に開催する”OPTiM TECH NIGHT”で話す予定の「社製ROS nodeのコードを直してCPU負荷を減らしたホボROSと関係ない話」のプレビュー版として、対応中に社内から飛んできた”マサカリ”をご紹介します。内容にご興味持たれた方は10/24の”OPTiM TECH NIGHT”へ是非ご参加ください。
OPTiMでのROSの使い方
当社はソフトウェア開発を主力とする企業としては珍しく、冒頭で紹介したBL-02やドローンといったハードウェアの開発にも力を入れています。
(下記は当社エンジニア坂井のプレゼン資料)
また、IoT分野ではセンサーデータ、カメラ画像の取得・解析は必須事項と言えます。特に、現在私が参画しているプロジェクトでは特殊なプロトコルの利用、マシンスペックの制約等があるため、それら諸問題を回避しつつCloud IoT OSへデータを集積するためにもROSを活用しています。
実装したROS nodeについて
機器の構成(概略)は下記の通りです。*2
カメラからデータを読み取り、別のnodeが受け取れるように受け流すnodeを実装しました。
- USB接続のカメラから画像データを読み取る
- 読み取ったデータを他のnodeに無加工で受け流す
当初の構成では、カメラとのインタフェースはROSの既存モジュール(libuvc_camera等)の利用を見込んでおり、ロジックの実装はスクリプト言語で行う想定でした。ところが調査した限りでは、ROSのカメラモジュールはROS内で画像処理することを前提に作られており*3、Motion JPEG/JPEGで取り込んだ場合でも内部でビットマップ形式への変換が走ります。今回利用するエッジデバイスは前述の通りマシンスペックに制約があるため、余分な画像処理によるCPUのオーバーヘッドが無視出来ません。そこで、スクリプト言語利用の前提から、低レイヤーの実装も可能なC++で直接カメラとのインタフェース部分から実装することにしました。*4
初期の実装は2週間程度で完了しました。それまでは、GolangとJavaとを少し触り、稀にC言語のコードを直すぬくぬく生活を送っていた事もあって、一段落した日の日報にはゴリゴリのメモリ管理に対する怨嗟の内容が記載されています。(↓当時の日報、原文ママ)
■コメント
topで見ていたら順調にメモリ使用量が増えていく現象に名前をつけてください。。。orz
昨日のテックリード会で奥村さんがC++はスレッドやら色々辛いと言っていたのが如実に思い出されました、、、
■今日工夫した所
boostライブラリを駆使した
■今日の仕事を後30分効率化できたやり方
boost使いすぎたかも知れません。。。
今思い返すと、バイネームで当社エグゼグティブエンジニア奥村の名前を出したのが運の尽きだったのかも知れません。。
マサカリが飛んできた
翌日に奥村から以下の返信を貰いました。
メモリリーク、リソースリーク、ハンドルリーク、etc...
C++11以降だと boost を使う機会がかなり減った印象ですが
何のために boost を使っているんですか?
こちらに対する私の返信は以下の通りです。*5
> メモリリーク、リソースリーク、ハンドルリーク、etc...
( ゚д゚)マジレス イタダキマシタ!
この後、社内のgitリポジトリにコードを共有して*6、長文で真面目なやり取りを一巡終え、一段落した所でR&Dチームの久保から別の返信を貰いました。
足立さん:
なにやら盛り上がっていたのでソースコードをチラ見しましたが,
http://xxxx/xxx.cpp#L208
で xxx_ctrl::output_jpeg を呼び出して
http://xxxx/yyy.cpp##L16
によって ptr ポインタの指す先が新たに new で確保されたアドレスに変わっていますが,それを解放していませんね.
ここでリークしているんじゃないかと思います.
該当部分のソースは↓のような感じでした。
189 void xxx_core::data_read() 190 { 191 this->is_term_data_read = false; 192 //this->cam_buffer.set_capacity(60); 193 //this->cam_buffer_size.set_capacity(60); 194 init_camera(camera_ctrl::MJPEG); 195 while(!this->is_term_data_read) { 196 int32_t cam_buf_sz = this->cam_ctrl.get_buffer_size(); 197 uint8_t* cam_buf = new uint8_t[cam_buf_sz]; 198 //boost::shared_ptr<uint8_t*> cam_buf(new uint8_t(cam_buf_sz)); 199 //boost::shared_array<uint8_t> cam_buf(new uint8_t(cam_buf_sz)); 200 //boost::scoped_array<uint8_t> cam_buf(new uint8_t[cam_buf_sz]); 201 uint8_t* _ptr; 202 uint8_t* tm_ptr = NULL; 203 DEBUG_PRINT("alloc addr = 0x%08X\n", (uint32_t)cam_buf); 204 size_t sz = this->cam_ctrl.read_camera(&cam_buf); 205 #ifdef __DEFINEの定義__ 206 _ptr = cam_buf; 207 #else /* __DEFINEの定義__ */ 208 sz = jpg_ctrl.output_jpeg(&_ptr, cam_buf, sz); 209 #endif /* __DEFINEの定義__ */ 210 if(this->cam_buffer.size() > 30) {
この時の私は「そんな見落とし私がするわけ無いじゃん」と言う発想が有ったため
ざんねん、そこは動かしてないのです( ̄ー ̄)ニヤッ
__DEFINEの定義__ を定義しているのってここですよね?
http://xxxx/xxxx.cpp#L240そうすると,
http://xxxx/xxxx.cpp#L205
の時点では else の方に入るのでそこが動いちゃってることになると思いますが.
「そんなバカな・・・」と思いつつ確認をすると。。。
205 #ifdef __DEFINEの定義__ 206 _ptr = cam_buf; 207 #else /* __DEFINEの定義__ */ 208 sz = jpg_ctrl.output_jpeg(&_ptr, cam_buf, sz); 209 #endif /* __DEFINEの定義__ */ 210 if(this->cam_buffer.size() > 30) { ~~~~(略)~~~~ 240 #define __DEFINEの定義__ 241 void xxx_core::publish() 242 {
指摘箇所を修正(L240の定義をL188に移動)した結果、、、
正解
増えなくなりました ... orz完全放置したつもりだったのに、ビルドが通って動いていたようです ... orz orz
このやり取りには、普段は日報に余り反応してくれないディレクターの谷口からもサムズアップが付きました。
終わりに
本稿執筆の目的はぼっちLTの回避にあります。
本イベントでは、会場が2ステージ構成になっています。参加者はステージ間を随時、自由に移動可能です。講演者は当社選りすぐりのエンジニア陣*8が担当します。このことが何を指し示すかと申しますと、例えばOPTiMのAI開発を頂点から主導する奥村と同じ枠の裏になってしまうと、私の聴講者はゼロと言う可能性も有り得ます。
よろしくどうぞ、お願いいたします。