X

Android 4.4 Sensor Batchingに対応して消費電力を抑える

Sensor Batchingの概要

Android 4.4ではセンサーへのアクセスをバッチ処理することで消費電力をさらに抑えました。センサーバッチ(Sensor batching)ではシステムがスリープしていても(画面がオフでも)センサーイベントを取得できるため位置情報を利用するなど低消費電力・長時間利用というユースケースに最適です。

バッチ処理が必要な理由

バッチが実行されるまでの間はセンサーイベントが通知されないためCPUは低消費電力なアイドル状態/サスペンド状態を維持できます。バッチの実行間隔は任意の設定が可能です。もちろんリアルタイムなセンサー値がほしい場合はいままで通りです。センサーのバッチ処理はフィットネスや位置追跡、監視などに向いており、システムスリープ状態やスクリーンオフでも利用できる利点があります。ハードウェア対応が必要なため、Nexus 5など新しい端末で利用できます [Sensorを利用する(TechBooster過去記事)]

バッチモードを有効にする

バッチモードを有効にするとバッチ実行間隔を設定できます。SensorManagerに設定用メソッドが追加されています。

  • public boolean registerListener (SensorEventListener listener, Sensor sensor, int rateUs, int maxBatchReportLatencyUs)
  • public boolean registerListener (SensorEventListener listener, Sensor sensor, int rateUs, int maxBatchReportLatencyUs, Handler handler)

SensorManager#registerListenerメソッドの引数は次の通りです。

引数名 説明
SensorEventListener listener センサーイベントを受信するリスナー
Sensor sensor 通知を登録するセンサー
int rateUs 通知間隔の設定。SENSOR_DELAY_NORMALからFASTESTまでの4種類の定数値かマイクロ秒で指定します
int maxBatchReportLatencyUs バッチ間隔(マイクロ秒)。0の場合、バッチ処理は無効

registerListenerメソッドの戻り値がtrueであればバッチモードが有効です。falseであればバッチモードをサポートしていません

バッチ処理に対応していない場合はすべてのセンサーにおいて変更が即座に通知されます。バッチモードが有効であれば第4引数のmaxBatchReportLatencyマイクロ秒間隔でバッチ(まとめて)処理されてセンサーイベントが遅延発生します。

第3引数のrateUsには、バッチ処理ではない通常のセンサーイベント間隔を指定できます。SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME, SENSOR_DELAY_FASTESTの4種類のほか、Android 2.3以降ではマイクロ秒単位で通知間隔が設定できます(が、あくまでシステムへのヒントであり間隔が保障されているわけではありません)

バッチ通知で受け取るセンサーイベントの特徴

バッチ処理の間に起きたセンサーイベントはすべて記録され、バッチ実行時のタイミングで一斉に通知します(センサーイベントはリスナー経由で通知されますが、引数のSensorEventに含まれるタイムスタンプは時系列順が保証されています)。しかしバッチ間隔が長いと、古いデータが捨てられることがあります。

SensorEventListener#onSensorChanged(SensorEvent event)メソッドで通知されるSensorEvent.sensorクラスの追加メソッドを見てみましょう

Sencorメソッド名 説明
int getFifoMaxEventCount() FIFOバッファの最大保持イベント数
int getFifoReservedEventCount() FIFOバッファにたまっているイベント数


getFifoMaxEventCountメソッドではバッチ処理中に記憶できる最大のイベント数が、getFifoReservedEventCountメソッドではバッチ処理の結果、実際に受け取れるイベント数が入っています。

バッチ処理ではSoCやCPUへの割り込みを減らすことができるため、CPUの消費電力を抑えるアイドル状態に入りやすくなります。条件によっては、より低消費電力なSuspend状態へも移行します。サスペンド状態に入るとアプリケーションの動作が止まってしまいますが、センサーのバッチ処理は継続できます。

サスペンド状態ではCPUが停止しているので収集したセンサーイベントをハードウェアFIFOバッファに格納していきます。CPU(アプリケーションプロセッサ)が起きる前にFIFOバッファが一杯になった場合、古いイベントから失われます。(センサーイベントを欠落させたくない場合、WAKE_LOCKを利用してください。WALE_LOCK取得中はサスペンド状態に入らず、FIFOバッファが一杯になることはありません。ただし低消費電力でのバッチ処理というメリットが得られなくなります)。

バッチ処理はベストエフォートで提供されます。別のアプリケーションがバッチ処理を要求していた場合、複数のアプリケーションへのセンサー通知を連続的に実行します。

バッチ処理用FIFOバッファを空にする

SensorManager#flushメソッドとandroid.hardware.SensorEventListener2インターフェイスを使えばFIFOバッファを空に(Flush)できます。

メソッド名 説明
SensorManager#flush (SensorEventListener listener) FIFOバッファを空する。非同期実行
SensorEventListener2#onFlushCompleted(Sensor sensor) 完了通知用リスナー


flushメソッドを呼び出したとき、FIFOバッファにセンサーイベントが残っていた場合は通常のSensorEventListenerリスナー経由で通知されます。
flushメソッドは非同期で実行され、SensorEventListener2#onFlushCompleted(Sensor sensor)メソッドでセンサーごと完了が通知されます。

Androidプラットフォーム(HAL)でのバッチ処理の実装

以降は次のURL(英語)の翻訳+意図が伝わりやすいように追加編集しています。おもにAndroid端末そのものの開発に携わるシステム設計者、プラットフォーム開発者に向けた詳細情報です。

CPUの状態と消費電力

一般的なCPUでは、電源が入ってタスクを実行しているRun状態、電源が入っているが何もしていないIdle状態、さらに低消費電力モードであるSuspend状態があります。Suspend状態での消費電力はRun状態の1/100と言われています。Nexus 5等ではバッチ処理のために内部的なハードウェアとしてFIFOバッファを搭載しています。Run状態であるならFIFOに入ったセンサー値を適切に処理できるため、センサー値の取得漏れはありません。

Suspend状態ではこのFIFOがバッチ処理の役に立ちます。FIFOがいっぱいになったとしても古いイベントを上書きし、循環バッファのように振る舞えます。

CPU(またはSoC)がSuspend状態から復帰した場合は、FIFO内を探して直近のセンサ値を取得、複数のセンサー情報をまとめて(バッチ処理して)通知します。

Android端末を構成するFIFOバッファ部分が対応していればという条件がありますが、FIFOが一杯になったことをハードウェアで検出してCPUをSuspend状態から強制的に復帰させることができます。WAKE_UPON_FIFO_FULLモードと呼ばれるオプション機能(必須ではない)です。しかし頻繁にSuspend状態から復帰しては低消費電力という特徴を失ってしまうため最低でも10秒分のFIFOバッファが性能要件として定められています。

バッチ処理の実行要件

バッチモードをサポートする場合、ハードウェアFIFOが必要です。HAL層にソフトウェア的に実装してしまうとCPUがSuspend状態にならず、電力を節約する目的に合致しません。SoCやCPUの手助けを受けずに実施する必要があります。

ハードウェアFIFOの実装によりますが、いくつかのセンサーが1つのFIFOバッファを共有することも可能です。センサーのバッチ処理間隔の設計許容値が
– 加速度センサーのバッチ処理間隔:最大20秒
– ジャイロスコープのバッチ処理間隔:最大5秒
であった場合、加速度センサーの通知タイミングをジャイロスコープに合わせて5秒間隔とすることもできます(あくまでAndroid端末を開発するメーカー側の実装に依存)

FIFOの割り当て優先度

FIFOバッファのサイズには限りがあるため、システム設計時に各センサーごと優先度に基づいて予約領域を指定できます。

優先度高:低消費電力モードでの歩行検出。ターゲットとなるバッチ間隔は20秒~1分
歩行検出は比較的長いバッチ間隔を設定します。これは即応性が低く低消費電力であることが優先されているためです。

優先度高:Idle状態での行動検出やジェスチャー認識。ターゲットとなるバッチ間隔は3秒
ユーザーの行動やジェスチャーの検出は3秒程度のバッチ処理が推奨されています。

優先度中:高頻度な割り込み負荷の軽減。ターゲットとなるバッチ間隔は1秒未満
たとえばジャイロスコープなど高周波数センサーなどは秒間240回(240Hz)程度駆動できます。これらを10回まとめてバッチ処理(通知は1回に減るが)できれば秒間24回に割り込みを減らせます。

優先度中:低頻度な割り込み負荷軽減。ターゲットとなるバッチ間隔は1分以上
温度センサや気圧センサ、即座に数値が変わらないセンサーは頻繁なチェックは必要としません。バッチ処理に対応することで低消費電力な監視用途に利用できます

優先度中:すべてのセンサー値の収集。ターゲットとなるバッチ間隔は1分以上
FIFOバッファのサイズが十分にある場合にのみ考慮してください。Suspend状態であってもセンサデータをすべて残しておけるだけのサイズがあれば最も低消費電力です。

※WAKE_UPON_FIFO_FULLを実装する場合はFIFOが一杯になったタイミングでバッチ処理が必要です。ハードウェア割り込みから復帰することになりますがその間(数msかかることがありますが)もセンサーデータを欠落しないように注意してください。

以上、おつかれさまでした。

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org