パフォーマンス最適化、歩きは止まらないーーメモリ編

プロジェクトのパフォーマンスの最適化は、主にCPU、GPU、メモリの3つの方面を中心に行われます。ゲームであれ、VRアプリケーションであれ、メモリ管理は開発段階の最優先事項です。

ただし、私たちが評価した大量のプロジェクトでは、90％以上でさまざまな程度のメモリ使用問題が発生しています。

現在、Unityエンジンに基づくモバイルゲームとモバイルVRゲームに対して、メモリコストは次の3つの部分に集めています：
**1.アセットメモリ使用率; 
2.エンジンモジュール自身のメモリ使用率; 
3.マネージヒープメモリ使用率。**
プロジェクトにメモリの問題があれば、上記の3つの状況に避けられません。今日は、これらの3つの状況を1つずつ説明します。

---
#アセットメモリ使用率
比較的複雑な大規模および中規模のプロジェクトでは、アセットのメモリ使用量が総メモリの70％以上を占めることがよくあります。だから、アセットの使用が適切かどうかは、プロジェクトのメモリ使用量を直接決定します。一般的に、ゲームプロジェクトのアセットは、テクスチャ（Texture）、メッシュ（Mesh）、アニメーションクリップ（AnimationClip）、オーディオクリップ（AudioClip）、マテリアル（Material）、シェーダー（Shader）、フォント（Font）およびテキストアセット（Text Asset）などのタイプに分類できます。その中、テクスチャ、メッシュ、アニメーションクリップ、オーディオクリップ四つのアセットは、大きなメモリコストを引き起こす可能性が最も高いアセットです。
##一、テクスチャ
テクスチャアセットは、ほとんど全てのゲームプロジェクトで最大のメモリコストを占めるアセットであります。60,000パッチのシーンの場合、最大メッシュアセットは10MBのみですが、2048x2048テクスチャは直接16MBに達する場合があります。だから、プロジェクトでテクスチャアセットを適切に使うかどうかは、プロジェクトのメモリ使用量に大きく影響します。

では、テクスチャアセットを使用する場合、何に注意すべきですか？

###（1）テクスチャ形式
テクスチャ形式は、開発チームにとって最も重要なテクスチャ属性です。これは、テクスチャのメモリ使用量だけに影響するではなく、テクスチャのロード効率も決定するためです。一般的に、開発チームにハードウェアのタイプに応じて、サポートできるテクスチャ形式をできるだけ選択することをお勧めします。例えば、AndroidプラットフォームのETC、iOSプラットフォームのPVRTC、Windows PCのDXTなど。**そのため、UWAレポートでは、開発チームが迅速に問題を特定できるように、テクスチャ形式を詳細にリストしています。**
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/89d48d6d-4d2a-92b0-0e9b-a24a04484042.png)
ハードウェアでサポートされているテクスチャフォーマットを使用すると、次の問題が発生する場合があります。

####カラースケール問題
ETCやPVRTCなどの形式はすべて非可逆圧縮であるため、テクスチャの色差の範囲が大きい場合、必然的にさまざまな程度の「階段」のようなカラースケールの問題が発生します。したがって、多くの開発チームはより良い結果を達成するためにRGBA32 / ARGB32形式を使用しています。ただし、この方法は大きいメモリ使用率を導きます。例えば、同じ一枚の1024x1024テクスチャそしてMipmapをオンにしない場合、PVRTC形式のメモリ使用率は512KBになり、RGBA32ビットに変換すると、おそらく4MBに上昇します。だから、開発チームがRGBA32またはARGB32形式のテクスチャを使用する場合は慎重に検討する必要があります。より賢い選択は、テクスチャの色差の範囲を最小限に抑え、ハードウェアがサポートする圧縮形式をできるだけ多く使用して保存することです。

####ETC1は透過チャンネルをサポートしていない問題
Androidプラットフォームでは、OpenGL ES 2.0を使用するデバイスに対して、テクスチャ形式はETC1形式のみをサポートできます。この形式にはより深刻な問題があります。それは、Alpha透明度チャネルをサポートしないため、透明テクスチャをETC1形式で直接保存できません。この点に関して、開発チームに透明テクスチャをできる限り2枚に分割することをお勧めします。つまり、一枚のRGB24ビットテクスチャは元のテクスチャのカラー部分を記録し、もう一枚のAlpha8テクスチャは元のテクスチャの透過チャンネル部分を記録します。そして、これらの2つのテクスチャを次々にETC1形式のテクスチャに変換し、特定のシェーダーでレンダリングして、透禍テクスチャをサポートする効果を実現します。**この方法では、RGBA透禍テクスチャのレンダリング効果を大幅に近似できるだけでなく、テクスチャのメモリ使用量を減らすこともできます。**

もちろん、現在OpenGL ES 3.0をサポートしているデバイスが増えているため、AndroidプラットフォームでETC2またはASTCをさらに使用できます。これらのテクスチャ形式は、透禍チャネルをサポートし、より理想的な圧縮率を持つテクスチャ形式であります。ゲームがミドルおよびハイエンドデバイスのユーザーに適している場合は、これらの2つの形式をテクスチャの主な保存形式として直接使用することもできます。

###（2）テクスチャサイズ
一般的に、テクスチャサイズが大きいほど、メモリ使用量も多くなります。だから、テクスチャサイズをできるだけ小さくし、512x512テクスチャで表示効果が十分である場合は、1024x1024テクスチャを使用しないでください。後者のメモリ使用量は前者の4倍であるためです。**そのため、UWAレポートでは、開発チームがすばやく検出できるように、テクスチャのサイズを詳細に表示します。**
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/160f1f70-954a-d5de-ffed-c8f3778db007.png)
###（3）Mipmap機能
Mipmapは、レンダリング帯域幅の圧力を効果的に軽減し、ゲームのレンダリング効率を向上させることを目的としていますが、Mipmapをオンにすれば、テクスチャメモリが元の1.33倍になります。より深度感のある3Dゲームに対して、通常は3DシーンモデルとキャラクターのMipmap機能をオンにすることをお勧めしますが、UWAがテストした沢山のプロジェクトでは、一部のUIテクスチャもMipmapをオンにすることがよくあります。実際にはこれは必要がないです。大多数のUIはスクリーンの最上層にレンダリングされます。Mipmapを有効にしてもレンダリング効率は向上できませんが、不要なメモリ使用量が増加します。したがって、開発チームにUWAレポートにMipmapで並べ、Mipmap機能をオンにさせるアセットはUIアセットですかどうかを確認することをお勧めします。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/91835b63-be58-7a70-cd23-b680a9922c8c.png)
###（4）Read＆Write
一般的に、Unityエンジンで、テクスチャアセットの「Read＆Write」機能がデフォルトでオフになっていますが、プロジェクトのディープな最適化に、多くのプロジェクトのテクスチャアセットがこのオプションをオンにすることに気づいてしました。これに対して、オンにするとテクスチャメモリが2倍になるため、開発チームにテクスチャアセットこのオプションの使用を厳密に注意することを提案します。
![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/f28b14e4-66f6-9511-cecb-32f0e3062a44.jpeg)

---
##二、メッシュ
メッシュアセットは、複雑なゲームでより多くのメモリを使用する場合があります。メッシュアセットについて、使用するときに注意すべき点は何ですか？
###（1）Normal、ColorやTangent
私たちが深く最適化した多数のプロジェクトでは、メッシュアセットのデータに、大量のColorデータ、Normalデータ、Tangentデータが含まれていることがよくあります。これらのデータの存在は、メッシュアセットのファイルサイズとメモリ使用量が大幅に増加させます。このうち、ColorデータとNormalデータは、主に3DMaxやMayaなどのモデリングソフトウェアをエクスポートするときに生成されますが、Tangentは一般的にエンジンにインポートするときに生成されます。

さらに面倒くさいのは、プロジェクトがメッシュにDraw Call Batching操作を行いと、全体的なメモリ使用量がさらに増える可能性があることです。たとえば、100個のメッシュが合併する場合、そのうち99個にはColor、Tangent、や他の属性がなく、残り1つにはColor、Normal、およびTangent属性が含まれます。このため、UWAレポートで各メッシュのNormal、Color、およびTangent属性の具体的な使用状況を示しています。開発チームは、各属性によって並べ替えて表示でき、冗長データを持つアセットを直接特定することができます。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/e11307ca-ec77-5f04-bbcb-6767dd20ed21.png)
一般的に、これらのデータは主にShaderがよりカッコいい効果を生成するために使用されます。だから、開発チームにプロジェクトのメッシュアセットを詳細なチェックをし、モデルのレンダリングシェーダーにこれらのデータをレンダリングする必要はあるかどうかを確認しますことをお勧めします。

記事の長さのせいで、本日はテクスチャアセットとメッシュアセットのみをご紹介しますが、アニメーションクリップやオーディオクリップなどの他のアセットについては、UWAパフォーマンスレポートで直接確認することをお勧めします。同時に、次のアセットトピックで詳しく説明するので、しばらくお待ちください。
####エンジンモジュール自身のメモリ使用率
エンジン自体のメモリコストは複雑で、大量の「小さな」メモリによって蓄積されていると認められます。例えば、GameObjectまたは他の各種Component（最大量なComponentはTransformはずです）、ParticleSystem、MonoScriptおよびさまざまなManager（SceneManager、CanvasManager、PersistentManagerなど)。

通常の状況では、上記のエンジンの各コンポーネントのメモリコストは比較的小さく、実際にメモリコストが大きいのは、WebStreamとSerializedFileの2つです。そのメモリ割り当てのほとんどは、AssetBundleのアセットローディングが原因です。簡単に言えば、new WWWまたはCreateFromMemoryでAssetBundleをロードする時に、Unityエンジンは元のデータをメモリにロードして解凍しますが、WebStreamのサイズは元のAssetBundleファイルサイズ+解凍されたデータのサイズ+ DecompressionBuffer（0.5MB）。同時に、Unityバージョン5.3以前のAssetBundleファイルはLZMAで圧縮されています。その圧縮率はZipに似ている（20％-25％）ため、1MBの元のAssetBundleファイルの場合、ロード後のWebStreamのサイズは5〜6MBになる場合があります。**そのため、プロジェクトがnew WWWで複数のAssetBundleファイルをロードし、またAssetBundleをすぐに解放できない場合、WebStreamのメモリが非常に大きくなる可能性があります。これは、開発チームが注意すべき点であります。**
![6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/8db75a85-d7cb-7124-2604-34c5c6373593.jpeg)
SerializedFileの場合は、LoadFromCacheOrDownload、CreateFromFile、またはnew WWWローカルAssetBundleファイルを使用するときに生成されるシリアル化ファイルです。

WebStreamおよびSerializedFileの場合、次の2つの点に注意する必要があります。

1)AssetBundleが完全にクリーンアップされていない状況があるかどうか。 開発チームは、Unityプロファイラーで具体的な使用状況を直接チェックし、Take Sample時のAssetBundleの存在が合理的かどうかを判断できます。

2)大きなWebStreamを占めるAssetBundleファイル（例えばUI Atlas関連のAssetBundleファイルなど）に対しで、LoadFromCacheOrDownLoadまたはCreateFromFileを使用して置き換える、つまり、解凍されたAssetBundleデータをローカルCacheに格納して使用することをお勧めします。このアプローチ、つまりメモリスペースをローカルディスクスペースに変えることは、メモリが特に限られているプロジェクトに非常に適しています。

####マネージヒープメモリ使用量
Unityエンジンに基づくほとんどの現在のプロジェクトでは、マネージヒープメモリはMonoによって割り当てられ、管理されます。「マネージ」の本来の目的は、Monoが必要なメモリに適応するようにヒープのサイズを自動的に変更し、不要なメモリを解放するためにガベージコレクション（Garbage Collection）操作を行いことです。それてコードメモリ管理における開発者への技術要求を削減します。

**ただし、これは、開発チームがコードに好きにマネージヒープメモリを添加することができるという意味ではありません。これは、現在Unityで使用されているMonoバージョンには重大な問題があるためです。Monoヒープメモリが割り当てられると、システムに返されません。 つまり、Monoのヒープメモリが増加するだけで減少しません。**例を挙げます。プロジェクトが実行する時、シーンAに60MBのマネージヒープメモリを開きました、次のシーンBでは20 MBのマネージヒープメモリのみが必要な場合、Monoには40 MBの空きヒープメモリがあり、 システムには戻されません。これは私たちに対して非常に見えたくない状況です。ゲーム（特にモバイルゲーム）に対してメモリ使用率はとても高価のものであり、Monoに大量の不必要なメモリをロックさせるのは非常に無駄なことです。**したがって、UWAレポートでは、開発チームのテスト中に累積された関数ヒープメモリ割り当てを統計しました。ヒープメモリ割り当てTop10の関数をチェックしたら、迅速に基礎コードの実現をチェックでき、不要なヒープメモリを割り当てるコードがあるかどうかを特定できます。**
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/bc32bbf0-29c6-69fa-8a29-effb8e709ba9.png)
ここを読んで、あなたはそのような質問をするかもしれません：**どの関数が大きなヒープメモリ割り当てを持っているか知っていますが、どうすれば不要なヒープメモリをさらに見つけることができますか？**

これは私たちがよくあった問題です。そのため、プロジェクトディープ最適化サービスに、プロジェクトチームに直接入って、現場でプロジェクトコードをチェックして問題コードを特定します。たくさんのディープテストをした後、ユーザーの不要なヒープメモリの割り当ては、主に下記の方面から起こします。

1)高频率のNew Class/Container/Arrayなど。**開発チームは、Update、FixUpdate、またはコール頻度の高い関数でヒープメモリを開かないように注意する必要があります。これにより、プロジェクトのメモリとパフォーマンスが大幅に低下します。**簡単に計算します。プロジェクトのある関数がフレームごとに100Bのヒープメモリを割り当て、フレームレートが1秒あたり30フレームであると仮定します。そして1秒のゲームのヒープメモリ割り当ては3KB、1分のヒープメモリ割り当ては180KBで、10分後に1.8MBが割り当てられています。そのような関数が10個あれば、10分後にヒープメモリの割り当ては18MBになります。この期間中に、Monoのヒープメモリのピーク値の上昇やGCの複数コールを導く可能性が高いです。私たちがテストしたプロジェクトでは、関数が10分以内に数百MBを割り当てるのがたくさんあって、時々にGB当たりのメモリ割り当て状況もあります。

2)Log出力。多くのプロジェクトで、まだたくさんのLog出力があることがわかりました。開発チームに自身のログの出力を厳密に制御し、不要なヒープメモリ割り当てを回避するためにキーログのみを保持することをお勧めします。**これに関して、UWAレポートにLogの出力を詳しく検査します。詳細なパフォーマンスコストだけではなく、同時にLog出力のコールパスも表示します。そうすれば開発チームはレポートで直接にLogの出力を特定や制御できます。**
![8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/0c601085-fc02-6a38-584e-2b6e33e67e0c.jpeg)
3)UIPanel.LateUpdate。これは、NGUIでCPUとヒープメモリのコストが最も高い関数です。自身はただ一つの関数でありますが、NGUIの大量の使用がそれを無視できないルールに徐々になされています。この関数のヒープメモリ割り当てと自身のCPUコストは、根本としては同じで、UIメッシュの再構築であります。
![9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/acd328c9-3fd7-4bf5-44c1-5274f0aeb007.jpeg)
コードヒープメモリの割り当てについて注意すべき点はまだたくさんあります。例えば、Stringリンク、一部のエンジンAPI（GetComponent）の使用など、これらは常に検討しまして、ここには記事の長さの原因で紹介しません。興味があれば、Googleで検索できます。以後にはコード効率に関する特別トピックもありますので、しばらくお待ちください。

---
#UWAテストのメモリ標準
みなさんがUWAを使用した後、UWAが推奨するメモリ標準値について多くの疑問があります。 ここでは、UWAメモリ標準を作成するためのルールも紹介します。

（1）150MBの全体的なメモリ標準は、主に次の2つの要因から導き出されます。

①**たくさんのプロジェクト最適化の後にまとめて得ました。**実際、現在市場である主流のUnityゲームでは、メモリ使用量は主に120〜200MBに集中しています。同時に、iPhone4や512MB / 768MBなどのローエンドのAndroidモデルを考慮に入れると、そのアプリケーションの総メモリ使用量は200MBを超えることはできません（iPhone4の安全線は約180MBになるはずです）。ですから、UWAはReserved Totalを150MBに設定しました、これはUnityエンジン自身のメモリの割り当てです。目的はアプリがシステムライブラリを使用した後、OSの総なメモリも200MB未満になるためです。

②**一部のチャネルは、AndroidゲームのPSSメモリに厳しい制限を課しています。**通常、ゲームのPSSメモリは200MB未満である必要があります。これは、UWAがReserved Totalメモリを150MBに設定するもう1つの重要な理由です。

（2）総メモリが150MBに設定されると、さらに具体的な割り当てに設定をしました。ただし、説明したいのは、ここでのメモリ割り当ては実際には厳密な式を示せるものではなく、ただたくさんのプロジェクト最適化作業で私たちが得た経験であります。現在、プロジェクトの適切なメモリ割り当ては次のとおりです。

●テクスチャアセット：50 MB
●グリッドアセット：20 MB
●アニメーションクリップ：15 MB
●オーディオクリップ：15 MB
●Monoヒープメモリ：40 MB
●その他：10 MB

より複雑なフォントファイル（Microsoft Yaheiなど）とテキストアセットは150MBで含まれていないことに注意してください。これらはゲームの要求次第で決定する必要があります。

（3）現在のUWAメモリ標準要求は少し厳しく、ミッドからハイエンドのデバイスに対して、許容量は実際には150MBをはるかに超えています。しかし、厳密な基準は、開発プロセスにおけるプロジェクトにとって良いものであると私たちは主張しています。少なくとも、それは誰にでも思い出させることができ、誰もが常に自分の問題に注意を払うことができます。私たちの了解により、現在のローエンド携帯電話のカバレッジ率は依然として非常に高いです。同時に、ミッドからハイエンドのモバイルデバイスに対しても、UWAもう実験と研究を行っています。遠くない将来に、さまざまなレベルのさまざまな設備に対してより合理的な推奨値を提供できるようになり、誰もがメモリをより簡単に管理できるようになれることを心から期待しています。

---

上記はゲームプロジェクトにある主なメモリ割り当て状況であります。ここを読んだみなさんに、Unityプロジェクトのメモリコストと潜在的な問題をよりよく理解し、自分のプロジェクトに対してより適切な最適化方法を見つけることを祈っています。

それ以外、開発チームに注意すべき点はまだ２点あり、これはメモリリークとアセットの冗長性です。次のメモリ最適化記事に関する内容を紹介します。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com