Unity 3 D里foreach,usingとCoroutineのGC問題の探究と解決策
6625 ワード
Foreach:
多くのUnity 3 Dの最適化テクニックは、いくつかの会社の筆記試験問題でforeachがGC Allocを生成することに関連しています.そのため、ゲームの実行時、特にUpdateではforeachを使用するこの注意事項をできるだけ避けなければなりません.
foreachは本当にGC Allocを生みますか?次のテストを行います.(Unity 3 D 5.4.0)
スクリプトの作成cs:
新しい空のシーンのカメラにスクリプトを掛け、各コードブロックのコメントを順番に削除し、Profilerを開き、CPUモジュールを表示し、GC Alloc項目でソートして、次の結果を得ます.
Array for
Array foreach
List for
List foreach
List GetEnumerator
Dictionary foreach
Dictionay GetEnumerator
以上からforeach対配列にはGC Allocはないことが分かるが,ListとDictionaryコンテナにはGC Allocがあり,foreachと反復によるGC Allocはいずれも40 Bである.
C#では、foreach文はマイクロソフトが提供する文法糖であり、C#内蔵反復器の使用複雑さを簡略化することができます.コンパイラはforeach文のコンパイル時にGetEnumeratorとMoveNextメソッドとCurrent属性を呼び出すコードを生成します.これらのコードと属性はC#内蔵反復器で提供されているので、上のforeach文と対応する反復器文は等価なので、GC Allocも同じです.
では、反復文はなぜGC Allocを生み出すのでしょうか.Dictionayを例にとると(同様にListに適用),dic.GetEnumerator()はDictionaryを返します.Enumerator、これはStructですが、iはIEnumeratorタイプの参照です.ここでは、値タイプ変数を参照タイプ変数に変換する必要があります.梱包操作を1回実行する必要があります.梱包操作はCPUとメモリリソースを追加で消費する必要があります(参照:http://www.cnblogs.com/yukaizhao/archive/2011/10/18/csharp_box_unbox_1.html)ということでGC Allocが生まれます.
では、私たちは_i Dictionaryと宣言する.Enumeratorタイプは、箱詰め操作はありませんが、GCALOcはありますか?テスト結果は次のとおりです.
private Dictionary.Enumerator _i;
本当にGC Allocはありません!
ではforeachはなぜこのような箱詰め操作を必要としないコードを生成しないのでしょうか.これは実は初期Mono C#コンパイラのバグで、その後のバージョンは修復されましたが、現在Unity 3 D用は未修復のバージョンです.の
Unity 5.3.5 f 1は1つのアップグレードパッケージを提供して、5.3.5 p 8にアップグレードすることができて、中にはMono C#コンパイラに対するアップグレードが含まれて、Mono 4.4にアップグレードしてこの問題を修復することができて、リンクアドレス:
http://forum.unity3d.com/threads/upgraded-c-compiler-on-5-3-5p8.417363/
このアップグレードパッケージをUnity 3 D 5.3.5 f 1にインストールすると、foreachは確かにGC Allocがないことがわかりますが、5.3.6および現在最新の5.4.0でforeachのGC Allocの問題がまだ存在し、このアップグレードパッケージは5.3.5テスト用だけで、後続のUnity 3 DバージョンのMono C#コンパイラを正式にアップグレードしていないと推測されます.では、コンパイラのバージョンを自分でアップグレードできますか?もちろんいいです.真似してもいいです.https://bitbucket.org/jbruening/unity-c-5.0-and-6.0-integrationより新しいコンパイラにアップグレードする方法を提供します.
もう一つの折衷的な解決策は、一般的に私たちのC#論理コードがAssembly-Charpにコンパイルされます.dllでは、このコンパイルプロセスはUnity 3 Dが指定したC#コンパイラで自動的に行われます.論理コードをVSでdllにコンパイルし、Assembly-CSharpにします.dll引用でGC Allocは生まれないのでしょうか?テストした結果、この方法は実行可能である:)~
また、上記のusing()の使い方でもGC Allocが発生し、箱を分解して箱詰めしたもの(具体的には上のコードを参照)でも、同じ5.3.5 p 8コンパイラのアップグレード後にGC Allocは発生しません.
だからforeachがGC Allocを生み出す問題については以下のように提案します.
1.ListやDictionaryなどの容器の遍歴にはforeachは使用せず、forと反復器(上で最適化したもの)を使用でき、配列foreachには安心して使用できる:)
2.Unity 3 D 5.3.5 f 1バージョンについて、公式提供の5.3.5 p 8アップグレードパッケージをインストールしてみると、foreachとusingを安心して使用できます:)
3.C#コンパイラバージョンのアップグレード(現在のプロジェクトのみ、Unity独自のC#コンパイラのアップグレードではありません)
4.論理コードはVS単独でdllにコンパイルすれば、foreachとusingを安心して使える
5.もしあなたが5.3.5バージョンでなくてもコードを単独でdllにコンパイルしたくないなら、foreachとusing(便利だと思います)を使いたいなら、Updateでは絶対に使わないことをお勧めします!!!
Coroutine:
CoroutineでもGC Allocは発生しますか?私たちは以下のテストを行います:(Unity 3 D 5.3.5 f 1、なぜこのバージョンを選んだのか、以下を見てから分かります:))
コードを作成するcs:
新しい空のシーンのカメラにスクリプトを掛け、Profilerを開き、CPUモジュールを表示し、GC Allocアイテムでソートして、次の結果を得ます.
Coroutineは確かにGC Allocが発生するようですが、この問題は5.3.6バージョンで修正され、5.3.6バージョンの発行説明を確認します(https://unity3d.com/cn/unity/whats-new/unity-5.3.6)が表示されます.
5.3.6および最新の5.4.0 CoroutineではGC Allocが発生しないことが確認されました.また、上記の5.3.5 f 1をインストールしたアップグレードパッケージが5.3.5 p 8にアップグレードされた後も、このGCの問題はなくなりました.
では、5.3.5以前のバージョンの開発者はどうすればいいのでしょうか.答えはUnity 3 DのCoroutineを使わず、自分でセットを実現すること..ただし、AssetStoreで重複造輪が代替案More Effective Coroutines(MECと略称)を見つけないようにするには、次の手順に従います.https://www.assetstore.unity3d.com/en/#!/content/54975は、名前の通りGCの問題を回避するのでより効率的です.プラグインには詳細なチュートリアルがあります.ここでは説明しないで、直接テストします.
TestCoroutine.csは以下のように変更されました.
テスト結果は次のとおりです.
確かにGC Allocは発生しませんので、5.3.5以前のバージョンではUnity 3 DのCoroutineの代わりにこのプラグインを使用できます.また、いくつか注意すべき点があります.
1.このプラグインで使用するAPIの一部はUnity 5のみであるため.xならではなのでUnity 4を使えばxこれらのAPIに使用されるコードを#if UNITY_とする必要がある5#endifパッケージを無効にします.
2.直接Timingを使用する場合.RunCoroutineの場合は、まず彼の戻りを保存し、現在のスクリプトが破棄されるときにOnDestroy()にTimingを追加する必要があります.KillCoroutines(前の戻り)は、現在のスクリプトのgameObjectに直接Timingコンポーネントを掛け、gameObject.GetComponent.RunCoroutineは、廃棄時にKillCoroutinesを使わなくなります.自動的に廃棄されるからです.
多くのUnity 3 Dの最適化テクニックは、いくつかの会社の筆記試験問題でforeachがGC Allocを生成することに関連しています.そのため、ゲームの実行時、特にUpdateではforeachを使用するこの注意事項をできるだけ避けなければなりません.
foreachは本当にGC Allocを生みますか?次のテストを行います.(Unity 3 D 5.4.0)
スクリプトの作成cs:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class TestForeach : MonoBehaviour
{
private int[] _arr = new int[] { 1, 2, 3 };
private List _list = new List();
private Dictionary _dic = new Dictionary();
//private IEnumerator _i;
private Dictionary.Enumerator _i;
void Start()
{
_list.Add(1);
_list.Add(2);
_list.Add(3);
_dic.Add(1, 1);
_dic.Add(2, 2);
_dic.Add(3, 3);
}
void Update()
{
#region Array
// code Array for
//for (int ii = 0; ii < _list.Count; ++ii)
//{}
// code Array foreach
//foreach (int i in _arr)
//{}
#endregion
#region List
// code List for
//for (int ii = 0; ii < _list.Count; ++ii)
//{}
// code List foreach
//foreach (int i in _list)
//{}
// foreach unity C# :
//IEnumerator iter = _list.GetEnumerator();
//while (iter.MoveNext())
//{ }
// foreach unity5.3.5p8 C# :
//List.Enumerator iter = _list.GetEnumerator();
//while (iter.MoveNext())
//{ }
//_i = _list.GetEnumerator();
//while (_i.MoveNext())
//{}
#endregion
#region Dictionary
//foreach (var m in _dic)
//{}
//_i = _dic.GetEnumerator();
//while (_i.MoveNext())
//{}
//using(Dictionary.Enumerator iter = _dic.GetEnumerator())
//{
// while (iter.MoveNext())
// { }
//}
// using unity C# :
//Dictionary.Enumerator iter = _dic.GetEnumerator();
//try
//{ }
//finally
//{
// ((IDisposable)iter).Dispose();
//}
// unity5.3.5p8 :
//Dictionary.Enumerator iter = _dic.GetEnumerator();
//try
//{ }
//finally
//{
// iter.Dispose();
//}
#endregion
}
}
新しい空のシーンのカメラにスクリプトを掛け、各コードブロックのコメントを順番に削除し、Profilerを開き、CPUモジュールを表示し、GC Alloc項目でソートして、次の結果を得ます.
Array for
Array foreach
List for
List foreach
List GetEnumerator
Dictionary foreach
Dictionay GetEnumerator
以上からforeach対配列にはGC Allocはないことが分かるが,ListとDictionaryコンテナにはGC Allocがあり,foreachと反復によるGC Allocはいずれも40 Bである.
C#では、foreach文はマイクロソフトが提供する文法糖であり、C#内蔵反復器の使用複雑さを簡略化することができます.コンパイラはforeach文のコンパイル時にGetEnumeratorとMoveNextメソッドとCurrent属性を呼び出すコードを生成します.これらのコードと属性はC#内蔵反復器で提供されているので、上のforeach文と対応する反復器文は等価なので、GC Allocも同じです.
では、反復文はなぜGC Allocを生み出すのでしょうか.Dictionayを例にとると(同様にListに適用),dic.GetEnumerator()はDictionaryを返します.Enumerator、これはStructですが、iはIEnumeratorタイプの参照です.ここでは、値タイプ変数を参照タイプ変数に変換する必要があります.梱包操作を1回実行する必要があります.梱包操作はCPUとメモリリソースを追加で消費する必要があります(参照:http://www.cnblogs.com/yukaizhao/archive/2011/10/18/csharp_box_unbox_1.html)ということでGC Allocが生まれます.
では、私たちは_i Dictionaryと宣言する.Enumeratorタイプは、箱詰め操作はありませんが、GCALOcはありますか?テスト結果は次のとおりです.
private Dictionary.Enumerator _i;
本当にGC Allocはありません!
ではforeachはなぜこのような箱詰め操作を必要としないコードを生成しないのでしょうか.これは実は初期Mono C#コンパイラのバグで、その後のバージョンは修復されましたが、現在Unity 3 D用は未修復のバージョンです.の
Unity 5.3.5 f 1は1つのアップグレードパッケージを提供して、5.3.5 p 8にアップグレードすることができて、中にはMono C#コンパイラに対するアップグレードが含まれて、Mono 4.4にアップグレードしてこの問題を修復することができて、リンクアドレス:
http://forum.unity3d.com/threads/upgraded-c-compiler-on-5-3-5p8.417363/
このアップグレードパッケージをUnity 3 D 5.3.5 f 1にインストールすると、foreachは確かにGC Allocがないことがわかりますが、5.3.6および現在最新の5.4.0でforeachのGC Allocの問題がまだ存在し、このアップグレードパッケージは5.3.5テスト用だけで、後続のUnity 3 DバージョンのMono C#コンパイラを正式にアップグレードしていないと推測されます.では、コンパイラのバージョンを自分でアップグレードできますか?もちろんいいです.真似してもいいです.https://bitbucket.org/jbruening/unity-c-5.0-and-6.0-integrationより新しいコンパイラにアップグレードする方法を提供します.
もう一つの折衷的な解決策は、一般的に私たちのC#論理コードがAssembly-Charpにコンパイルされます.dllでは、このコンパイルプロセスはUnity 3 Dが指定したC#コンパイラで自動的に行われます.論理コードをVSでdllにコンパイルし、Assembly-CSharpにします.dll引用でGC Allocは生まれないのでしょうか?テストした結果、この方法は実行可能である:)~
また、上記のusing()の使い方でもGC Allocが発生し、箱を分解して箱詰めしたもの(具体的には上のコードを参照)でも、同じ5.3.5 p 8コンパイラのアップグレード後にGC Allocは発生しません.
だからforeachがGC Allocを生み出す問題については以下のように提案します.
1.ListやDictionaryなどの容器の遍歴にはforeachは使用せず、forと反復器(上で最適化したもの)を使用でき、配列foreachには安心して使用できる:)
2.Unity 3 D 5.3.5 f 1バージョンについて、公式提供の5.3.5 p 8アップグレードパッケージをインストールしてみると、foreachとusingを安心して使用できます:)
3.C#コンパイラバージョンのアップグレード(現在のプロジェクトのみ、Unity独自のC#コンパイラのアップグレードではありません)
4.論理コードはVS単独でdllにコンパイルすれば、foreachとusingを安心して使える
5.もしあなたが5.3.5バージョンでなくてもコードを単独でdllにコンパイルしたくないなら、foreachとusing(便利だと思います)を使いたいなら、Updateでは絶対に使わないことをお勧めします!!!
Coroutine:
CoroutineでもGC Allocは発生しますか?私たちは以下のテストを行います:(Unity 3 D 5.3.5 f 1、なぜこのバージョンを選んだのか、以下を見てから分かります:))
コードを作成するcs:
using UnityEngine;
using System.Collections;
public class TestCoroutine : MonoBehaviour
{
void Start()
{
StartCoroutine(_UnityCoroutine());
}
IEnumerator _UnityCoroutine()
{
while (true)
{
yield return null;
}
}
}
新しい空のシーンのカメラにスクリプトを掛け、Profilerを開き、CPUモジュールを表示し、GC Allocアイテムでソートして、次の結果を得ます.
Coroutineは確かにGC Allocが発生するようですが、この問題は5.3.6バージョンで修正され、5.3.6バージョンの発行説明を確認します(https://unity3d.com/cn/unity/whats-new/unity-5.3.6)が表示されます.
5.3.6および最新の5.4.0 CoroutineではGC Allocが発生しないことが確認されました.また、上記の5.3.5 f 1をインストールしたアップグレードパッケージが5.3.5 p 8にアップグレードされた後も、このGCの問題はなくなりました.
では、5.3.5以前のバージョンの開発者はどうすればいいのでしょうか.答えはUnity 3 DのCoroutineを使わず、自分でセットを実現すること..ただし、AssetStoreで重複造輪が代替案More Effective Coroutines(MECと略称)を見つけないようにするには、次の手順に従います.https://www.assetstore.unity3d.com/en/#!/content/54975は、名前の通りGCの問題を回避するのでより効率的です.プラグインには詳細なチュートリアルがあります.ここでは説明しないで、直接テストします.
TestCoroutine.csは以下のように変更されました.
using UnityEngine;
using System.Collections.Generic;
using MovementEffects;
public class TestCoroutine : MonoBehaviour
{
void Start()
{
Timing.RunCoroutine(_UnityCoroutine());
}
IEnumerator _UnityCoroutine()
{
while (true)
{
yield return 0;
}
}
}
テスト結果は次のとおりです.
確かにGC Allocは発生しませんので、5.3.5以前のバージョンではUnity 3 DのCoroutineの代わりにこのプラグインを使用できます.また、いくつか注意すべき点があります.
1.このプラグインで使用するAPIの一部はUnity 5のみであるため.xならではなのでUnity 4を使えばxこれらのAPIに使用されるコードを#if UNITY_とする必要がある5#endifパッケージを無効にします.
2.直接Timingを使用する場合.RunCoroutineの場合は、まず彼の戻りを保存し、現在のスクリプトが破棄されるときにOnDestroy()にTimingを追加する必要があります.KillCoroutines(前の戻り)は、現在のスクリプトのgameObjectに直接Timingコンポーネントを掛け、gameObject.GetComponent.RunCoroutineは、廃棄時にKillCoroutinesを使わなくなります.自動的に廃棄されるからです.