低価格スマートフォン – メモリ管理と Firefox OS での最適化

[これは Mozilla Hacks Blog の記事 Low price smartphones – memory management and optimization on Firefox OS の翻訳です]

メモリリークのデバッグやメモリリソースの破壊を防ぐためにフットプリントをメモリに作成する方法が知られています。今回、メモリ管理とメモリリソースの限られている Firefox OS での最適化について紹介します。

Firefox OS ではどのようにしてメモリを確保しているか?

Firefox OS で十分なメモリが無いときにメモリを確保するための 3 種類のイベントが存在します。

  1. Low memory killer (LMK)
  2. Memory pressure event
  3. Out of memory (OOM)

Low memory killer (LMK)

LMK は Android でプロセスを終了させる事でメモリリソースを確保するためのポリシーです。 Firefox OS にこの機能を統合しました。 キャッシュサイズ、もしくは入手可能なメモリが閾値よりも少なくなった場合、システムはメモリーを確保するために LMK をトリガーして oom_adj が0よりも大きいプロセスを終了させます。

閾値は b2g/app/b2g.js に定義してあります。以下の表の例を説明すると、

  • キャッシュサイズと入手可能なメモリが両方とも 20MB 以下の場合、oom_adj が 10(BACKGROUND) のプロセスが LMK によって終了されます。
  • キャッシュサイズと入手可能なメモリが両方とも 8MB 以下の場合、 oom_adj が 8(BACKGROUND_HOMESCREEN) のプロセスが LMK によって終了されます。
  • キャッシュサイズと入手可能なメモリが両方とも 4MB 以下の場合、oom_adj が 0(MASTER) のプロセスが LMK によって終了されます。

ところで、 LMK はプロセスを1つずつ終了します。だから b2g は LMK によってプロセスが終了された後にメモリリークが発生する事があります。

// The kernel can only accept 6 (OomScoreAdjust, KillUnderKB) pairs. But it is
// okay, kernel will still kill processes with larger OomScoreAdjust first even
// its OomScoreAdjust don't have a corresponding KillUnderKB.

pref("hal.processPriorityManager.gonk.MASTER.OomScoreAdjust", 0);
pref("hal.processPriorityManager.gonk.MASTER.KillUnderKB", 4096);
pref("hal.processPriorityManager.gonk.MASTER.Nice", 0);

pref("hal.processPriorityManager.gonk.PREALLOC.OomScoreAdjust", 67);
pref("hal.processPriorityManager.gonk.PREALLOC.Nice", 18);

pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.OomScoreAdjust", 67);
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.KillUnderKB", 5120);
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.Nice", 0);

pref("hal.processPriorityManager.gonk.FOREGROUND.OomScoreAdjust", 134);
pref("hal.processPriorityManager.gonk.FOREGROUND.KillUnderKB", 6144);
pref("hal.processPriorityManager.gonk.FOREGROUND.Nice", 1);

pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.OomScoreAdjust", 200);
pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.Nice", 1);

pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.OomScoreAdjust", 400);
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.KillUnderKB", 7168);
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.Nice", 7);

pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.OomScoreAdjust", 534);
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.KillUnderKB", 8192);
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.Nice", 18);

pref("hal.processPriorityManager.gonk.BACKGROUND.OomScoreAdjust", 667);
pref("hal.processPriorityManager.gonk.BACKGROUND.KillUnderKB", 20480);
pref("hal.processPriorityManager.gonk.BACKGROUND.Nice", 18);

Memory pressure event

Memory pressure event がトリガーされたとき、Memory pressure event 監視として登録されているサービスはキャッシュ(b2g の一時データ)を最小化したり対比させるなどの共通のアクションが実行されます。Memory pressure event の閾値は b2g/app/b2g.js で設定され、初期値(notifyLowMemUnderKB)は 14MB となっています。

// Fire a memory pressure event when the system has less than Xmb of memory
// remaining.  You should probably set this just above Y.KillUnderKB for
// the highest priority class Y that you want to make an effort to keep alive.
// (For example, we want BACKGROUND_PERCEIVABLE to stay alive.)  If you set
// this too high, then we'll send out a memory pressure event every Z seconds
// (see below), even while we have processes that we would happily kill in
// order to free up memory.
pref("hal.processPriorityManager.gonk.notifyLowMemUnderKB", 14336);

Out of memory (OOM)

Linux カーネルは OOM ポリシーを提供しています。 入手可能なメモリが定義した閾値よりも少ない場合、 OOM はメモリ使用量と oom_score_adj をもとに計算したスコアを通して終了するプロセスを選択します。操作すべてはシステムが十分なメモリリソースを獲得するためのものです。OOM がトリガーされる閾値の初期値(min_free_kbytes)は1352KB です。

3つのメモリーポリシーが Firefox OS 上で、どのように円滑に動作し、すばらしいパフォーマンスをだすか?

まずいくつかの問題となりうる事を紹介しましょう。もし3つのポリシーが競合したら何がおこるでしょうか?

  • Problem 1. もし LMK の閾値が望んでいたものより低い場合システムの動作は遅くなるでしょう。少ない閾値の場合、システムはメモリが不足している時にメモリを獲得するために、いかなるプロセスを終了する事はありません。そして、kswapd とCPU リソースの大量消費によりパフォーマンスは低下するでしょう。もし閾値が望んでいるものより多い場合、ユーザーは都合悪く感じるでしょう。なぜならアプリケーションは簡単に終了されるからです。
  • Problem 2. もしMemory pressure event の閾値が大きい場合、退避または最小かされたキャッシュのアクティビティは活発になるでしょう。メモリが足りている場合は関連するアクティビティは CPU リソースを浪費してパフォーマンスが低下するでしょう。もし閾値が小さい場合、システムは常にメモリーを解放する事が出来なくなります。
  • Problem 3. OOM が LMK よりも早くトリガーされた場合、クラッシュや fatal エラーが発生するでしょう。もしOOM が最初にトリガーされた場合、 B2G やシステムアプリケーションがバックグラウンド/フォアグランドアプリケーションよりも先に終了される事になります。メモリが不足している場合、システムは b2g やシステムアプリケーションよりも先にバックグラウンド/フォアグランドアプリケーションを終了させるべきです。

この問題について、我々は対応するために以下のパラメータを設定しています。

  1. 体感パフォーマンスをよくするために、我々はメモり獲得のためにバックグラウンドアプリを終了させる事を許可し、フォアグランドアプリケーション操作を円滑にしています。そのために、ホームスクリーンやバックグラウンドアプリを終了しやすいように、BACKGROUND_SCREEN.KillUnderKB と BACKGROUND.KillUnderKB の値を増やしています。
  2. 認知できるバックグラウンドアプリケーションを終了させる前に Memory pressure event を発火させています。これは、BACKGROUND_HOMESCREEN.KillUnderKB と BACKGROUND_PERCEIVABLE.KillUnderKB の間に notifyLowMemUnderKB の値を設定するのがよいためです。
  3. Problem 3 をの LMK は OOM より先に動作させる事を解決するために、min_free_kbytes はMASTER.KillUnderKB よりも低く設定しています。

Tarako (sc6821, 128MB メモリー端末) を例にとってみましょう。adb shell b2g-info によってダンプしたメモリ設定は以下の通りです。

Low-memory killer parameters:

  notify_trigger 14336 KB

  oom_adj min_free
        0  4096 KB
        1  5120 KB
        2  6144 KB
        6  7168 KB
        8 16384 KB
       10 18432 KB

メモリ調整のヒント

Firefox OS は b2g/app/b2g.js のパラメータで望んだ設定変更を検証するための効率的な方法が提供されています。
開発者は端末の /system/b2g/defaults/pref/dev-pref.js を変更された変更設定を追加する事が出来ます。このファイルのパラメータは b2g/app/b2g.js の初期値を上書きします。

低価格スマートフォンのメモリ最適化の簡素なやり方はあります。我々は bugzilla で詳細なテクニックを議論しています。ぜひ Firefox OS 開発に参加して、世界をふるわせてください。