しまもん 

モンスターラボ島根開発拠点のブログ

mruby/cのタイムスライスについて

はすみきん ・2020-06-02

もう半年も前のプルリクなのですが、mruby/c(ワンチップマイコン向けのRuby実装)のタイムスライスについての提案が upstreamにマージ されました。 これについての解説に需要が(少しだけ)ありそうなので、遅ればせながら記事にします。

と言っても、当該プルリクのコメントにだいたい書いてあります。同じこと(+もうちょい)を日本語で説明します。

やったこと

端的に書くと、「タイムスライス値」を「TICKユニット値 * TICKカウント値」に分けた。です。

何が問題だったのか? => sleep(1)が妙に長かった

下のキャプチャを見てください。 左がCRuby(MRI。要するにふつうのRuby)で、右がmruby/cです。

プログラムはどちらもまったく同じです:

t = 0
while true
  puts t
  sleep 1
  t += 1
end

両側のプログラムを同時にスタートさせ、適当なところでプログラムを止めた時点で、CRubyは34秒カウントしていたのに対し、mruby/cは24秒しかカウントしていません。 mruby/cの sleep(1) は約1.4秒もスリープしてしまっています。

原因は、「POSIX上のmruby/c」のタイムスライスに生じていた遅延です。

タイムスライスとは

mruby/cはOSレス(いわゆるベアメタル)で動作することが前提になっていて、「(Linuxぽく言うならば)ユーザ空間上のスレッド」を自前で管理し、タイムスライス毎にスレッド(マイコンの世界ではタスクと呼びます)を切り替えることで複数の処理を並行させています。

そのタイムスライス値は10msでした。 10msをどう数えていたかというと、1ms毎に割り込みを発生させ、10回カウントしたらタイムスライス終了→はい次のタスクが動いてください、ということです。

mruby/cの開発チームが最初に選択したターゲットマイコンは、サイプレス社のPSoC5LPでした。 これのハードウェアタイマ割り込みは、1ms(ミリ秒)が基本的な値ですので、そのまま採用したというわけです。

Linuxでは

Linuxはカーネル定数 HZ によってソフトウェアクロックの解像度を定義しており、定数名からわかるとおり周波数の値です。

値は、100, 250, 300, 1000のいずれかだそうです。 カーネル2.6.13以降のLinuxでは、デフォルト値が250です。 1秒間に250周期ですから、Linuxが正確に報告できる最小時間間隔は4msです。

プルリクがマージされるまでのmruby/cでは、POSIX(要するにLinuxとかWindowsとか)のTICKユニットも1msに設定されていたのですが、これは4msよりも小さいので、1msを計るためにハードウェアの物理クロックを数える高精度タイマ(HRTs)が頻繁に使われて誤差が積み重なり、結果として sleep(1) が遅くなっていたと思われます。

(Linuxの時計については man 7 time と打つと、より詳しい解説を読めます)

 

このほか、ESP32のFreeRTOSも portTICK_PERIOD_MS の値がデフォルトでは10msです。 やはりESP32でも1msのTICKは遅延を引き起こしていました。 計測データなどを残していませんが、手元のストップウォッチで確認できるくらいの遅延だったと記憶しています。

というわけで

「タイムスライス値」を「TICKユニット値 * TICKカウント値」という2値の積に分けることで、「その環境が保証可能なTICKユニット毎に割り込みを発生させ」、あとは「TICKカウント値の調整によってタイムスライス値がだいたい10msになるように調整できる」ようになりました。

タイムスライス値は10msでなくても構いません。 ユーザインタフェースへの反応が悪くなっても構わない && 重たい計算をがっつりやらせたいならば、もっと大きなタイムスライス時間をとってもいいかもしれません。

まとめ

電力を使いまくるデスクトップコンピュータよりも、乾電池で動く8mm角のマイコンのほうが小さなTICKユニットを正確に動かすというのは意外かもしれませんね。

マイコン(と言うかハードリアルタイム組み込みプログラミング)は入力されるデータの量や頻度を設計時にコントロールすることで、1msのTICKユニットを実用的なものにしています。たぶん。 たとえば、自動車のエンジンの燃料噴射タイミングはミリ秒の精度が必要(らしい)なので、1ms単位の割り込みが必要だし、実際そのとおりに動かすことができるようです。 (この辺は伝聞とか想像ばかりw)

 

プルリクのマージ後も、PSoC5LPではTICKユニット値が1msのままです。 酒IoTくらいのアプリケーションならば、とくに遅延が発生する様子はありません。

他方、一般のLinuxは入力が頻繁に、不規則に、しかも大きなデータ量でやってくるかもしれません。 これを効率よく処理するためにチューニングしていった結果が、現在の4msというTICK時間なのだと思います。

 

これからほかのマイコン(Wio Terminalとか)向けにmruby/cのHALを書く方は、TICKユニット値とTICKカウント値の組み合わせをいろいろ試して適切な値を見つけていただけたら幸いです。 たぶん消費電力にも影響があります。

つづく

HALについてはもうひとつ(ふたつかも)書きたいことがありますので、そのうち続きます。  

追記: 書きました

| 採用情報

城下町・松江で一旗揚げたいRailsエンジニア絶賛募集中!

Blue