WebGL のパフォーマンスについて
WebGL: ブラウザースクリプトとの相互作用

Memory in WebGL

Unity WebGL のメモリは、実行できるコンテンツの複雑さを制限する制約要因になる可能性があります。そのため、ここでは WebGL でメモリがどのように使われるかについて説明します。

WebGL のコンテンツはブラウザー内で実行されます。そのため、メモリはすべてブラウザーによってブラウザー内部のメモリ空間に割り当てられることになります。利用可能なメモリの総量は使用しているブラウザー、OS、デバイスによって大きく変動する可能性があります。決定要因には他にもブラウザーが 32 ビット 64 ビットのどちらか、ブラウザーがタブごとに分離したプロセスを使用するのか、それとも開かれているタブがすべて同じメモリ空間を共有するのか、ブラウザーの JavaScript エンジンがゲームのコードを変換するのにどれだけのメモリを要するか、などがあります。

Unity WebGL コンテンツがブラウザーに著しく大きなメモリの割り当てを必要とするエリアが、複数あります。

Unity Heap

これは Unity がすべての状況、管理されたオブジェクトやネイティブオブジェクト、現在読み込まれているアセットやシーンを保存するためのメモリです。他のプラットフォームで Unity プレイヤーが使用するメモリと似たようなものです。使用するメモリ量は Unity WebGL player settings から設定することができます(何度も書き出すのが嫌であれば、生成された HTML ファイルに記載されている TOTAL_MEMORY の値を編集することで同じ内容の操作をすることもできます)。 Unity Profiler を用いてこのメモリ量をサンプリングすることもできます。このメモリは JavaScript コードでバイト配列の TypedArray として生成され、このサイズのメモリが一続きの集まりとして割り当てできることを要求します。(メモリが断片化していてもブラウザーが割り当てできるように)この空間はできるだけ小さくしたいかと思われますが、ゲームに含まれるすべてのシーンに属するデータを再生できるだけの大きさが必要です。

Asset Data

Unity WebGL のビルドを作成する際、 Unity はコンテンツに必要なすべてのシーンとアセットを持つ .data ファイルを書き出します。WebGL には実際のファイルシステムがないため、このファイルはコンテンツの開始前にダウンロードされ、コンテンツが実行されている間はずっと非圧縮状態のデータがブラウザーメモリの連続したブロックに保持され続けることになります。そのためダウンロード時間とメモリ使用量の削減を両立するには、このデータをできる限り少なくするよう心掛けるといいでしょう。アセットのビルドサイズを最適化する方法についての情報は ファイルサイズの削減 を参照してください。

読み込み時間とメモリ使用量を減らすために他にできることは、アセットデータを アセットバンドル にまとめることです。そうすることでアセット読み込みのタイミングを完全に管理することができ、必要なくなったタイミングで破棄して使用されていたメモリを解放することもできます。 AssetBundle は直接 Unity ヒープに読み込まれ、ブラウザーによる追加の割り当てが発生することはありません。( WWW.LoadFromCacheOrDownload を使用して AssetBundle をキャッシングする場合はこの限りではありません。ブラウザーの IndexedDB にベイクされるメモリにマッピングされた仮想ファイルシステムを使用するためです。)

Memory needed to parse the code

メモリに関係するもう 1 つの問題はブラウザーの JavaScript エンジンに求められるメモリです。 Unity は何百万行という膨大な JavaScript コードファイルを発行しますが、これは通常ブラウザーで扱われる JavaScript コード量よりも多いのです。 JavaScript エンジンによってはこのコードをパース、最適化するためにより大きなデータ構造体を割り当てる可能性もあり、それによってコンテンツ読み込み時にメモリスパイクが数ギガバイトに及ぶ、というケースもありえるのです。 WebAssembly などの将来的な技術が行く行くはこの問題を解決してくれることを期待していますが、そのときまでにできる最善のアドバイスは発行するコードサイズを少なくすること、です。サイズの削減についてのより詳しい情報と実践方法については こちらを参照してください。

Dealing with memory issues

Unity WebGL ビルドでメモリ関係のエラーが出た場合、ブラウザーがメモリの割り当てに失敗しているのか、 Unity WebGL ランタイムが Unity ヒープの自演に割り当てられたブロック内の空いているブロックへ割り当てるのに失敗しているのかを理解することが重要です。ブラウザーがメモリの割り当てに失敗している場合、例えば Unity ヒープのサイズを削減するなどして、上記のメモリエリアのサイズ削減を試みるといいかもしれません。一方で、 Unity ランタイムが Unity ヒープ内のブロックに割り当てるのに失敗している場合、逆にそのサイズを増やすといいかもしれません。

Unity は、エラーメッセージが上記のどちらなのかを理解しようと試みます(そしてどうしたらいいかを提案します)。ブラウザーごとにメッセージの表示方法が違うため、表示が常に簡単というわけではなく、すべてを理解できないかもしれません。 「Out of memory」エラーがブラウザーで表示された場合、実行中のブラウザーがメモリ不足の問題を抱えている可能性があります (この場合 Unity ヒープのサイズを少なくするといいかもしれません)。また、 Unity コンテンツを読み込んでいるときに人間に理解可能なエラーメッセージの表示がなくブラウザーがクラッシュする場合があります。これには多くの理由が考えられますが、よくありうるものとしては JavaScript エンジンが生成されたコードをパースおよび最適化するために必要なメモリが多すぎることです。

Large-Allocation Http ヘッダー

サーバーはコンテンツの Large-Allocation http ヘッダーを生成することがあります。これはサポートされているブラウザーー (現在 Firefox のみ) にメモリが必要であることを伝え、分割されていないメモリ空間で新しい処理を発生させたり、大きなメモリ割り当てが成功するようにハウスキーピングの作業を行ったりします。 こうすることにより、特に 32 ビットブラウザーーで Unity ヒープを割り当てるときに、ブラウザーーがメモリ不足になる問題を解決できます。

ガベージコレクションへの配慮

Unity にマネージドオブジェクトを割り当てる場合、使われなくなったものはガベージコレクトされる必要があります。詳しい情報はドキュメントの automatic memory management を参照してください。 WebGL でも同じことが言えます。マネージドオブジェクト、ガベージコレクトされたメモリともに Unity ヒープ内に割り当てられます。

しかし、WebGL で気を付けなければいけないのは、ガベージコレクション (GC) を実行するタイミングに注意が必要なことです。ガベージコレクションを実行するために、通常 GC はすべてのスレッドの実行を一時停止し、スタックを精査し読み込み済みオブジェクトの参照を登録します。これは現在の JavaScript では不可能なことです。このため WebGL で GC はスタックが空になった場合にのみ実行されます(現在これは毎フレーム後に 1 度行われます)。この仕様はほとんどのマネージドメモリを保守的に扱うコンテンツでは問題になりません。そして、相対的に多めの GC 割り当てがフレームごとに行われます(Unity プロファイラーを用いてデバッグすることができます)。

However, the following code would fail running on WebGL, becuase it would not get a chance to run the GC between iterations of the loop, to free up memory used by all the intermediate string objects - which would eventually cause it to run out of memory in the Unity heap.

string hugeString = "";

for (int i = 0; i < 100000; i++)
{
    hugeString += "foo";
}

参考資料


2018–08–23 編集レビュー 無しに修正されたページ - ページのフィードバックを残す

WebGL のパフォーマンスについて
WebGL: ブラウザースクリプトとの相互作用