Cooooding!!

Unity(C#)を使ったゲーム開発関連Tipsなど

iOSでアプリのメモリ使用量を取得する【Unity】

概要

iOSでアプリがメモリ不足でキルされるまでにどれくらい余裕があるかを判定するためにメモリ使用量を取得する方法を調べました。取得できるメモリ量にはいろいろな種類があり、なかなか適切な取得方法がわからなかったので調べた結果わかったことをまとめます。

実装

結論に至るまでには紆余曲折あったのですが、まずは結論となる実装を書きます。

Native Plugin側

#import <mach/mach.h>

extern "C" 
{
	bool GetTaskVmInfo(task_vm_info_data_t *info)
	{
		mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
		if(task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)info, &count) != KERN_SUCCESS)
		{
			return false;
		}
		return true;
	}

	long GetMemorySize()
	{
		task_vm_info_data_t info;
		if(!GetTaskVmInfo(&info))
		{
			return -1;
		}
		return (long)(info.internal + info.compressed);
	}
}

この実装を書いた.mmファイルをAssets/Plugins/iOS以下に置きます。

C#

[DllImport("__Internal")]
public static extern long GetMemorySize();

上記の実装になった理由

resident_sizeで取得できる値

メモリ使用量を取得する方法を調べてみるとtask_basic_info_data_t.resident_sizeを取得しているサンプルがよく見つかります。これは物理メモリの使用量を取得するものではあるのですが、実際にiOS上での動作を確認してみるとアプリが確保したメモリ量とは異なった増減をすることがわかります。これに関してはっきり理由を書いた説明は見つからなかったのですが、いろいろ調べた情報と観察した挙動から察するにスワップのような機能やメモリを圧縮する機能があることなどが原因ではないかと思います。

iOSにあるスワップのような機能

iOSにもWindowsにあるスワップに似た機能があります。Windowsでは物理メモリが不足したときにHDDやSDDなどのストレージにデータを退避(ページアウト)して必要になったら再び物理メモリ上に戻されますが、iOSでは変更可能なメモリ(newやmallocで確保されるもの)がストレージにページアウトされることはありません。ストレージからロードしたデータで変更を加えていないもの(Cleanなメモリ)だけがページアウトされる可能性があります。

メモリを圧縮する機能

iOSではページアウトできるデータが限られていますが、その代わりメモリを圧縮する機能があります。これはCleanではないメモリ(Dirtyなメモリ)でも圧縮されます。圧縮されたメモリ量(おそらく圧縮前のサイズ)はtask_vm_info_data_t.compressedで取得できます。

resident_sizeではダメな理由

上記の事情により確保したメモリが増えるほどresident_sizeの値は増えにくくなります。実機で動作を観察してみるとresident_sizeの値がほとんど変わっていないのにも関わらずLow Memory Warningが発生したりキルされたりするので、メモリに余裕があるかどうかの判定にこの値は使えなさそうでした。

phys_footprintで取得できる値

task_vm_info_data_t.phys_footprintという値もあります。詳しい説明は見つかりませんでしたが挙動を観察してみると、アプリがnewした分この値も増えるわかりやすい挙動をしており、この値が特定の値を超えるとメモリ不足でアプリキルされるようでした。例えばiPhone XS(iOS13.3)なら2.04GiB、iPhone7(iOS11.2.6)なら1.37GiBを超えた辺りでアプリがキルされました。

また、この値はXcodeのDebug NavigatorのMemory Reportのメモリ量やInstrumentsのActivity MonitorのLive ProcessesのMemoryの値とも一致するようでした。

XcodeのDebug NavigatorのMemory Reportの値

InstrumentsのActivity MonitorのLive ProcessesのMemoryの値

ただ、iPhone6(iOS9.3.5)で確認してみると何故かXcodeの値と33MiBほどのズレがあるようでした。

internal + compressedにした理由

挙動を観察してみるとiPhone XS(iOS13.3)とiPhone7(iOS11.2.6)でphys_footprintの値はtask_vm_info_data_tのinternal + compressedとほぼ一致するようでした。iPhone6(iOS9.3.5)ではinternal + compressedの値がXodeの値とほぼ一致するようなのでphys_footprintよりもこちらの値を使った方が良さそうでした。

古いiPhoneでphys_footprintの値が異なる件についてはっきりとした情報はありませんでしたがおそらくiOSが古いことが原因ではないかと思います。iOS10がどうなるか気になりますが手元にiOS10の端末がないため確認できませんでした。何年か経って古いiOSのサポートをする必要がなくなったらphys_footprintの方を見た方が良くなるかもしれません。

環境

  • Unity2019.2.9f1
  • iPhoneXS (iOS13.3)
  • iPhone7 (iOS11.2.6)
  • iPhone6 (iOS9.3.5)