RTM-Lua

View the Project on GitHub Nobu19800/RTM-Lua

実験結果

以下の結果についてはWindows 10、Ubuntu 14.04、ev3dev(jessie)で計測していますが、UbuntuについてはParallels仮想環境で実験しているため、実環境のUbuntuとは結果が異なる場合があります。

実験に使用したソースコードは以下から入手できます。

RTC実行の時間

以下の手順で計測

  1. onActivate関数内で計測開始
  2. onExecute関数が1000回呼ばれた時点で非アクティブ化
  3. onDeactivate関数で計測終了

実行周期は1000000Hzに設定してあるため、ほとんど待機しないはずです。

onExecute関数内で処理をする場合

Luaはゲームに関連する処理に利用される頻度が多いため、実装例として10000体のキャラクター(矩形)のあたり判定をする処理で実験します。 Luaはテーブル、C++はmap、Pythonはディクショナリで実装します。

Windows

言語 結果[s]
Lua 4.3324
LuaJIT 1.2459
C++ 0.6142
Python 6.4802

LuaJITがPythonを圧倒してC++に次ぐ速度を記録しています。 LuaもPythonよりは速い。OpenRTM Lua版に未実装の機能がある分も影響しているかもしれません。

Ubuntu

言語 結果[s]
Lua 5.4076
LuaJIT 1.4919
C++ 0.2871
Python 9.4799

Windowsでの結果と比較すると、C++が高速化して他が劣化するという謎の結果になりました。 コンパイラの違いでこういうことが発生するのかは謎ですが、ちょっとPythonは遅すぎです。

ev3dev

言語 結果[s]
Lua 5.1760
LuaJIT 3.2487
C++ 1.3929
Python 20.4041

Pythonが論外すぎる。LuaがUbuntuの場合よりも高速化しているのが最大の謎。

onExecute関数内で処理をしない場合

Windows

言語 結果[s]
Lua 0.5343
LuaJIT 0.5372
C++ 0.6214
Python 0.1713

Python以外碌な結果になっていない。C++に至っては処理ありの場合よりも遅くなっている。 C++に関してはcoilのsleep関数の精度が悪いのが原因です。 Luaに関しても実行周期が遅い場合は問題なかったため、同様の理由だと思います。 この挙動はバグに近いため、OpenRTM-aistについては今後の修正に期待します。

Ubuntu

言語 結果[s]
Lua 0.03327
LuaJIT 0.01445
C++ 0.04378
Python 0.06448

色々と謎の残る結果です。 LuaとLuaJITがやたらと速くはなっていますが、未実装部分がどの程度影響を及ぼしているのかは気になります。

メモリ使用量

Windows

言語 結果[MB]
Lua 13.6
LuaJIT 8.6
C++ 1.5
Python 14.7

LuaとPythonでそれほどの差はありません。C++は他を圧倒しています。 単純にlua.exeを実行した場合は0.3MB、luajit.exeは0.4MB、python.exeは3.5MBのメモリ使用量だったので、もっと差がつくかと思ったのですが、あまり影響はないようです。 Pythonで使用しているomniORBはCモジュールで実装しているので、その分はメモリ使用量は低くなってもおかしくないはずですが、それでもメモリ使用量でPythonがLuaを上回っているということになります。 ちなみにLua、LuaJITのRTCはメモリ使用量の変動がかなり激しいです。 この実験では実行周期を10Hzと低めに設定してあるため大して変動していませんが、実行周期が高い場合は大きく変動するため注意が必要です。

Ubuntu

言語 結果[MB]
Lua 12.8
LuaJIT 8.3
C++ 1.7
Python 16.5

概ねWindowsと似た結果。 これらの結果だけ見ればPythonを使うメリットはあまり無いように見えるが、Luaにはマルチスレッド機能がないため不便なことが多い。

LuaやPythonでも普通のPCならば問題はありませんが、メモリが64MBのEV3で起動すると相当な負担になるので使い方を考える必要があります。

念のために詳細な結果も載せておきます。

言語 VmPeak[kB] VmSize[kB] VmLck[kB] VmPin[kB] VmHWM[kB] VmRSS[kB] VmData[kB] VmStk[kB] VmExe[kB] VmLib[kB] VmPTE[kB] VmSwap[kB] Threads State
Lua 49164 47344 0 0 30180 28276 27068 132 168 3480 112 0 1 running
LuaJIT 34592 34136 0 0 17652 17180 15996 132 432 3176 124 0 1 running
C++ 567016 567012 0 0 9984 9984 524920 132 88 12112 164 0 7 sleeping
Python 675864 675344 0 0 24900 24900 613948 132 2796 8580 228 0 7 sleeping
言語 VmPeak[kB] VmSize[kB] VmLck[kB] VmPin[kB] VmHWM[kB] VmRSS[kB] VmData[kB] VmStk[kB] VmExe[kB] VmLib[kB] VmPTE[kB] VmSwap[kB] Threads State
Lua 34892 34892 0 0 15828 15828 14616 132 168 3480 88 0 1 sleeping
LuaJIT 27296 27172 0 0 10388 10200 9032 132 432 3176 120 0 1 sleeping
C++ 558816 493284 0 0 9556 9556 451192 132 88 12112 148 0 6 sleeping
Python 440828 380164 0 0 22716 22716 318768 132 2796 8580 200 0 6 sleeping

VmPeak(最大仮想メモリサイズ)がC++とPythonの場合はLuaよりも桁違いに大きな値となっていますが、これは原因がよく分からないので詳しい人は教えてください。共有ライブラリをロードすると大きくなったりするのでしょうかね?

VmHWM(最大物理メモリサイズ)を見ると、Luaが大きな値になっています。 ただし、それは実行周期が速い場合であり、実行周期が遅い場合はPythonよりもLuaの方が小さくなっています。 基本的には、LuaのRTCで実行周期を上回るような処理をさせないほうがいいという事にはなります。

VmExe(実行ファイルのサイズ)をみると、Pythonが圧倒的に大きくなっており、次いでLuaJITが大きくなっています。 LuaJITをLuaと比較した場合に、地味にデメリットになりそうではあります。

VmLib(ロードされたライブラリのサイズ)を見ると、C++>Python>Luaとなっている。 C++、PythonはomniORBの共有ライブラリのロードが必要なため、その分大きくはなっているのだが、C++はさらにOpenRTM-aistの共有ライブラリのロードが必要なためさらに大きくなります。

スレッド数を見ると、Lua、LuaJITは当然1となっています。 C++、Pythonは7になっていますが、これは5~8で変動します。 ちなみにomniORBを起動するだけでスレッドが3つ起動するため、OpenRTM-aist関連のスレッドはメインスレッドを除くと2~5つという事になります。 とりあえず、OpenRTM-aistで起動するスレッドの数を数えてみました。

名前 説明
PeriodicExecutionContext 周期実行コンテキストのスレッド。RTCを駆動する。
Timer タイマースレッド。RTCが存在しないときのマネージャ自動終了等のイベントを実行する。設定で無効にできる。
get_component_profile RT System EditorがRTCのプロファイルを取得するオペレーションを呼び出すとスレッドが起動する
get_component_state RT System EditorがRTCの状態を取得するオペレーションを呼び出すとスレッドが起動する

実験では8つのスレッドが起動する場合があったので、もう一つスレッドが起動する場合があるはずですが、何のスレッドなのか特定できませんでした。 普通に使う分にはスレッドが多数起動してもあまり気にならないと思いますが、組込みシステム等で動かす場合はタイマースレッドを無効化する、RT System Editorからは極力操作しない等の工夫が必要です。

今回の実験で使用したRTCにはデータポートが存在しませんが、データポートがある場合はスレッド数が増える可能性があります。 コネクタ接続時にサブスクリプション型をnew、periodicに設定した場合は、コネクタの数だけスレッド数が増えるため、状況によっては注意が必要です。

ちなみにLuaでget_component_profileget_component_state等のオペレーションを呼び出した場合、コルーチンで順番に処理を進めるため、実行コンテキストの処理が遅れる可能性があります。

最後にStateを見てみるとC++、Pythonはsleeping、Luaはrunningになっています。 これらのデータはRTCが非アクティブ状態の時に計測していますが、C++、PythonがRTCが1つもアクティブ状態ではないときにスレッドの処理を一時停止するのに対して、Luaにはそのような機能はないため動き続けるということになっています。とは言っても、なかなか実装が難しいため手が出せていません。

RTC起動までの時間

以下の手順で計測

  1. PythonのsubprocessでRTC起動
  2. RTCのonInitialize関数内でPythonプロセスのCORBAオペレーション呼び出し
  3. PythonプロセスのCORBAオペレーション呼び出し時に時間計測

Luaはluacによるコンパイルで起動が高速化するとどこかで見たのですが、見ての通りあまり変わっていません。 まとめてluacを実行する場合は、以下のスクリプトをopenrtm-lua-x.y.z-x64-lua5.1直下にコピーして実行してください。

Windows

言語 結果[s]
Lua 0.7851
Lua(luac使用) 0.6749
LuaJIT 0.6276
C++ 0.1315
Python 0.3993

お、遅い。LuaとLuaJITがどうしてこんなに遅いのか。 シングルスレッドなのが影響しているのか、Pythonは一度実行するとバイトコードを生成するので速いのか。 ちなみにpycファイルを消した状態で実験すると、Pythonの場合は2秒程かかります。 そう考えるとLuaは凄まじく速いと言えなくもないです。

ちなみに実験ではスレーブマネージャに設定してマスターマネージャは外部で起動した状態で開始していますが、マスターマネージャが起動していない場合は+2秒ぐらいかかります。

Ubuntu

言語 結果[s]
Lua 0.510135
Lua(luac使用) 0.505378
LuaJIT 0.310405
C++ 0.015031
Python 0.082029

全体の傾向としてWindowsと違いはありません。

ev3dev

言語 結果[s]
Lua 60.1691
LuaJIT 31.1603
C++ 1.1395
Python 25.3410

C++と他との差が開きすぎです。 Luaが起動に1分もかかるのは、流石に許容できるレベルではなさそうです。 スクリプト言語を使う場合は、通常はプロセスを1つ起動してモジュールを複数ロードする使い方が正しいかもしれません。

メモリ削減の方法についての考察

実験結果を見ても分かる通り、Lua実行時に13MB、LuaJIT実行時に9MB程とメモリ消費量は少なくありません。 以下はLua、LuaJITでLuaの実行のみOiLの実行までIDLファイルの読み込みまでRTCの実行の条件でメモリ使用量を比較した結果です。

条件 結果[MB]
Luaの実行のみ 0.7
OiLの実行まで 4.4
IDLファイルの読み込みまで 8.5
RTCの実行 13.6
条件 結果[MB]
LuaJITの実行のみ 0.7
OiLの実行まで 3.1
IDLファイルの読み込みまで 7.5
RTCの実行 8.6

OiLの実行で3~4MBのメモリを使用するため、流石にC++実装のRTCのメモリ使用量を下回るのは無理そうです。

IDLファイルの読み込みで4MB以上激増しているため、この部分は削減する余地がありそうです。 RTSystemEditorからRTCのプロファイルを取得する、ポートを接続する、データをpush、pullする機能以外は全て削ってもいいかもしれません。 マネージャや実行コンテキストも削りましょう。

OpenRTM Luaにより増加する分についても、必要最低限の機能以外を削れば大分減りそうです。

大まかな見積もりですが、LuaJITの場合で4~5MB程度のメモリで実行できそうです。