forループがUnityのfor eachループよりも効率的であるのはなぜですか?


8

Unityがパフォーマンスについて話しているときに、for eachパフォーマンスを向上させるためにループを回避し、代わりに通常のforループを使用するように指示されました。実装の観点forとの違いは何for eachですか?for each非効率なものは何ですか?


1
配列内にある要素の数や使用する要素を数えることは別として、大きな違いはありません。簡単なグーグル検索で私はこれを見つけました:stackoverflow.com/questions/3430194/…(これはUnityにも適用されますが、言語が異なっていても、適用される基本ロジックは同じです)
John Hamilton

2
追加したいのは、最初の反復ではパフォーマンスを微調整しないことですが、優先順位は明確なアーキテクチャとコードスタイルでなければなりません。後で必要になった場合にのみ(パフォーマンスを)最適化します...
Marco

2
その講演へのリンクはありますか?
Vaillancourt

2
このUnityページによると、for eachループが配列以外のものを反復処理すると、ガベージが生成されます(Unityバージョン5.5より前)
Hellium

2
これは一般的なプログラミングであるため、トピックから外れるとの投票がありましたが、特にUnityのコンテキストで尋ねられ、これはUnityのバージョン間で変更された動作なので、参照のためにここを開いておく価値があると思いますUnityゲーム開発者。
DMGregory

回答:


13

foreachループは、見かけ上、渡したコレクションからIEnumeratorオブジェクトを作成し、その上を移動します。したがって、次のようなループ:

foreach(var entry in collection)
{
    entry.doThing();
}

これは、次のようなものに変換されます
(列挙子が後で破棄される方法に関する複雑さを排除します)。

var enumerator = collection.GetEnumerator();
while(enumerator.MoveNext()) {
   var entry = enumerator.Current;
   entry.doThing();
}

これが単純または直接コンパイルされた場合、その列挙型オブジェクトはヒープに割り当てられます(少し時間がかかります)。その基礎となる型が構造体(ボクシングと呼ばれるもの)であってもです。ループが完了すると、この割り当ては後でクリーンアップするためのガベージになり、コレクターがフルスイープを実行するのに十分なガベージが山積みになると、ゲームが一瞬途切れることがあります。最後に、コンパイラがそのアクションをインライン化しない場合、MoveNextメソッドとCurrentプロパティは、で直接項目を取得するよりも多くの関数呼び出しステップを含む可能性がありますcollection[i]

上記のUnityドキュメントのHelliumリンクは、ネイティブアレイ以外のものを反復処理した場合、この単純な形式がバージョン5.5より前の Unityで発生するものとまったく同じであることを示しています

バージョン5.6以降(2017バージョンを含む)では、この不要なボクシングはなくなりました。具体的なタイプ(例:int[]またはList<GameObject>またはQueue<T>)を使用している場合、不要な割り当ては発生しません。

問題のメソッドが、ある種のIListまたは他のインターフェイスで動作していることだけを知っている場合でも、割り当てを取得できます。これらの場合、動作している特定の列挙子がわからないため、最初にIEnumeratorオブジェクトにボックス化する必要があります。 。

したがって、これは、現在のUnity開発ではそれほど重要ではない古いアドバイスである可能性が高いです。私たちは物事についてのビット迷信ことができるようにUnityは、開発者に使用ので、塩の粒とそのフォームのいずれかのアドバイスを取り、古いバージョンでは遅い/の毛深いします。;)エンジンは私たちが信じているよりも速く進化します。

実際には、ゲームでforeachループを使用することによる顕著な遅延やガベージコレクションの問題が発生することはありませんが、実際の距離はさまざまです。選択したハードウェアでゲームのプロファイルを作成して、心配する価値があるかどうかを確認します。必要に応じて、問題が発生した場合に、開発の後半でforeachループを明示的なforループに置き換えることは非常に簡単です。そのため、開発の早い段階で読みやすさとコーディングの容易さを犠牲にしません。


確認するだけで、a List<MyCustomType>およびa IEnumerator<MyCustomType> getListEnumerator() { }は問題ありIEnumerator getListEnumerator() { }ませんが、メソッドはそうではありません。
Draco18sは、

0

オブジェクトのコレクションまたは配列(プリミティブデータ型以外のすべての要素の配列)にfor eachループが使用されている場合、GC(ガベージコレクター)が呼び出され、for eachループの最後で参照変数のスペースが解放されます。

foreach (Gameobject temp in Collection)
{
    //Code Do Task
}

一方、forループはインデックスを使用して要素を反復処理するために使用されるため、プリミティブデータ型は非プリミティブデータ型と比較してパフォーマンスに影響を与えません。

for (int i = 0; i < Collection.length; i++){
    //Get reference using index i;
    //Code Do Task
}

これについての引用はありますか?tempここで、変数はそう、(それはすでにヒープ上の既存のエントリへの参照のみなので、その参照型のインスタンスを保持するために、新しいヒープメモリを割り当てていない)コレクションが参照型の完全である場合でも、スタックに割り当てることができますここでゴミが発生するとは思わないでしょう。列挙子自体は、状況によってはボックス化されてヒープになる可能性がありますが、それらのケースはプリミティブデータ型にも適用されます。
DMGregory
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.