ゲームでメモリを効率的に管理する

Android プラットフォームでは、システムはできるだけ多くのシステムメモリ(RAM)を使用するよう試み、必要に応じてさまざまなメモリ最適化を行い、空き容量を確保します。こうした最適化はゲームに悪影響(速度が低下する、完全に強制終了するなど)を及ぼすことがあります。こうした最適化の詳細については、プロセス間のメモリ割り当てをご覧ください。

このページでは、ゲームに影響を及ぼすメモリ不足状態を回避するための手順について説明します。

onTrimMemory() に応答する

システムは onTrimMemory() を使用して、メモリが不足していてアプリが強制終了される可能性があることをアプリに通知します。多くの場合、アプリが受け取る警告はこれだけです。このコールバックはローメモリ キラー(LMK)に比べてレイテンシが大きいため、コールバックに迅速に応答することが重要です。

このコールバックに応じて、割り当ての速度、数、サイズを減らします。onTrimMemory() は重大度を示す定数を渡しますが、onTrimMemory() で反応できる範囲を超えて迅速に割り当てられる可能性があるため、最初の警告に応答する必要があります。

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        when (level) {
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> // Respond to low memory condition
            else -> Unit
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
              // Respond to low memory condition
                break;
            default:
                break;

C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

class LowMemoryTrigger : MonoBehaviour
{
    private void Start()
    {
        Application.lowMemory += OnLowMemory;
    }
    private void OnLowMemory()
    {
        // Respond to low memory condition (e.g., Resources.UnloadUnusedAssets())
    }
}

控えめなメモリ配分にする

メモリを控えめに配分して、メモリ不足を回避します。検討すべき項目には次のようなものがあります。

  • 物理 RAM のサイズ: ゲームは多くの場合、デバイスの物理 RAM 量の 1/4 から 1/2 を使用します。
  • 最大 zRAM サイズ: zRAM が多いほど、ゲームに割り当てられるメモリが多くなる可能性があります。この量はデバイスによって異なります。この値は、/proc/meminfoSwapTotal を探すと見つかります。
  • OS のメモリ使用量: システム プロセスに多くの RAM を割り当てるデバイスでは、ゲームに割り当てられるメモリが少なくなります。システムは、システム プロセスを強制終了する前にゲームのプロセスを強制終了します。
  • インストール済みのアプリのメモリ使用量: 多数のアプリがインストールされたデバイスでゲームをテストします。ソーシャル メディア アプリとチャットアプリは常に動作する必要があり、メモリの空き容量に影響を及ぼします。

控えめなメモリ配分を実現できない場合は、より柔軟なアプローチを取ります。システムでメモリ不足の問題が発生した場合は、ゲームが使用しているメモリ量を減らします。たとえば、onTrimMemory() に応じて低解像度のテクスチャを割り当てるか、格納するシェーダーを減らします。このメモリ割り当ての動的アプローチでは、特にゲーム設計フェーズで、デベロッパーに多くの作業が必要となります。

スラッシングを回避する

スラッシングは、空きメモリは少ないが、ゲームを強制終了するほどではない場合に発生します。このような状況では、ゲームにまだ必要なページを kswapd が回収したため、メモリからページを再読み込みしようとします。空き容量が不足しているため、ページはスワップアウトされ続けます(連続スワップ)。システム トレースはこの状況を、kswapd が継続的に実行されるスレッドとして報告します。

スラッシングの症状として、フレーム時間が長いことが挙げられます。1 秒以上の場合もあります。この状況を解決するには、ゲームのメモリ フットプリントを減らします。

利用可能なツールを使用する

Android には、システムがメモリを管理する仕組みについて理解する際に役立つツールが用意されています。

Meminfo

このツールは、メモリの統計情報を収集して、割り当てられた PSS メモリの容量とメモリが使用されたカテゴリを表示します。

次のいずれかの方法で meminfo 統計情報を出力します。

  • adb shell dumpsys meminfo package-name コマンドを使用する。
  • Android Debug API から MemoryInfo 呼び出しを使用する。

PrivateDirty 統計情報は、ディスクにページングできず他のプロセスと共有されていない、プロセス内の RAM の容量を示します。プロセスが強制終了されると、この容量の大部分をシステムで利用できるようになります。

メモリ トレースポイント

メモリ トレースポイントは、ゲームが使用している RSS メモリの量を追跡します。RSS メモリ使用量の計算は、PSS 使用量の計算よりはるかに高速です。計算が高速であるため、RSS はメモリサイズの変化をより詳細に示し、ピーク時のメモリ使用量をより正確に測定します。そのため、ゲームのメモリ不足を引き起こす可能性のあるピークに気づきやすくなります。

Perfetto と長いトレース

Perfetto は、デバイスのパフォーマンスとメモリの情報を収集し、ウェブベースの UI に表示するツールのスイートです。任意の長いトレースをサポートしているため、RSS の経時的な変化を確認できます。オフライン処理用に生成したデータに対して SQL クエリを発行することもできます。システム トレースアプリから長いトレースを有効にします。トレースに対して memory:Memory カテゴリが有効になっていることを確認します。

heapprofd

heapprofd は、Perfetto の一部であるメモリ トラッキング ツールです。このツールは、malloc を使用してメモリが割り当てられた場所を示すことで、メモリリークの発見に役立ちます。heapprofd は Python スクリプトを使用して起動できます。このツールはオーバーヘッドが少ないため、他のツール(Malloc Debug など)のようにパフォーマンスに影響することはありません。

bugreport

bugreport は、ゲームがメモリ不足でクラッシュしたのかどうかを調べるロギングツールです。ツールの出力は、logcat を使用するよりもはるかに詳細です。ゲームがメモリ不足でクラッシュしたのか、LMK によって強制終了されたのかが示されるため、メモリのデバッグに役立ちます。

詳細については、バグレポートのキャプチャと確認をご覧ください。