2017/10/13

Android Performance. Dropped frame

SystemEvents, Input Events, Application, Service, Alarm, UI Drawingといった多くの処理はMain Thread(UI Thread) で実行されます.
重要なポイントは, 画面は16ミリ秒の間隔で再描画されているということです.

Why 16ms, Why 60fps?

人間は繋がりのある複数枚の絵が十分な速さで連続していると, それがあたかもアニメーションしているかのように錯覚します. パラパラ漫画やアニメGifの原理です.
アニメーションをスムーズに見せるために, どれだけ素早く画像を表示できるかという点が重要で, 滑らかで流れるようなアニメーションには必要不可欠な要素です.

人間の脳がアニメーションしているように感じるためには, 最低でも12fps程度の速度が必要です. これよりも遅いとパラパラ漫画のようなぎこちない見た目になります. 12fpsという速度はアニメーションには見えてもあまりスムーズには映りません.
24fpsは流れるようなアニメーションに見えますが, これはモーションブラーやビジュアルエフェクトの効果によるものです.
60fpsはモーションブラーやエフェクトなしでスムーズに映ります. これ以上のfpsはほぼ感知できない領域です.

注意すべきは人間の目の明敏さで, フレームレートが60fpsから24fpsに落ちると, 途端にアニメーションのスムーズさを欠いたように感じ, よくない印象を与えることになります.

VSYNC

スムーズなアニメーションを実現するためにも, Androidがどのようにして60fpsを実現しているのかを理解しておきましょう. それには2つの用語を理解しておく必要があります.

リフレッシュレート

1秒間に画面を何回リフレッシュできるかの値で, ハードウェアが定めた一定間隔で実行されます.
単位はHz(ヘルツ)で, 例えば60Hzであれば1秒間に60回のリフレッシュが可能です.

フレームレート

GPUが一秒間で幾つのフレームを描画できるかの値です.
単位はfpsで, 例えば60fpsであれば一秒間に60フレームの描画が可能です.

Synchronized

GPUが画像データを出力し, ハードウェアがそれを画面に表示します.
スクリーンの描画は, これを何度も繰り返しているので, GPUとハードウェアはできる限り一緒に働くことが望ましいのですが, リフレッシュレートとフレームレートは同じ頻度で起こることが保証されていません.

フレームレートがリフレッシュレートより早いと, ティアリングという現象が発生します.
これは, GPUが新しいフレームをメモリに上書きしている最中に, 画面がリフレッシュされてしまい, まだ更新中の画像を描画してしまうことで, 画像が崩れる(部分的に古いフレームが残る)現象です. これを解決するのがダブルバッファリングです.

ダブルバッファリングでは, GPUがバックバッファにフレームを描画し, それが終わるとフレームバッファーと呼ばれる領域にコピーします. 画面をリフレッシュするときはこのフレームバッファから取り出してリフレッシュするわけです. これによって古いフレームへの上書きが行われないので, 中途半端に上書きされた状態にはなりません.

ここで注意しないといけないことのは, 画面のリフレッシュ中にバックバッファからフレームバッファへのコピー作業が発生しないようにすることです. そうしないと, 同じ問題が起こります. ここで登場するのがVSYNC(Vertical Synchronization)です.

通常はフレームレートがリフレッシュレートよりも高いことが望ましいです. なぜなら, 画面を読み込むよりもGPUのリフレッシュの方が早くなるからです.
GPUはフレームをバックバッファに載せると, VSYNCによって次の画面リフレッシュまで処理を待つことになります.

しかし, 反対にフレームレートがリフレッシュレートよりも低い場合, 例えば30fpsに対して60Hzのディスプレイであった場合, フレームバッファのリフレッシュ作業には, 画面リフレッシュの倍の時間を要するため, 同じフレーム内容で2回ずつリフレッシュすることになります.
問題は, これが断続的に起こった場合です.

十分に早いフレームレートで動作しても, 突然フレームレートが落ちると, ユーザはスムーズなアニメーションに続いて, ぶつ切りになったものを見ることになります.
これらの事象は一般的に ラグ, ジャンク, ヒッチング, スタッター と呼ばれます.

アプリの開発者はこれらの事象を避けなければなりません.
人間の目は明敏で, フレームレートが落ちると, 途端にアニメーションのスムーズさを欠いたように感じ, よくない印象を与えてしまうことを思い出してください.

アプリ開発者が目指すところは 常に60fpsのパフォーマンスを維持すること です.

1000ms / 60frames = 16.666ms/frame

MainThreadでは16msの間隔でUI Drawingイベントが発生します. 60fpsの滑らかなアニメーションを実現するためには16ms間隔の描画が必要になります.

Main Thread(UI Thread)

単一スレッドの処理は逐次実行されるため, 順番に処理されていきます. MainThreadも例外ではありません. UI DrawingイベントもMainThreadで実行されるので, もしあなたの処理が長引くとUI Drawingイベントが遅延し, 次のリフレッシュレートのタイミングを逃してしまい, アニメーションで描画されるはずであったフレームが抜け落ちる ドロップフレーム が発生します.
あなたが書いた処理の後には, 常にUI Drawingイベントが待ち構えていることを忘れないでください.

次回に続きます…