April 21, 2015

パフォーマンスを高めるために TArray の使用を最適化する

作成 Zak Middleton

アンリアル エンジンでは、TArray は動的にサイズ調整される型付きの要素の配列です。 TArray はプログラマにとって非常に便利なものであり、エピックのコードベースでも *数多く* 使われています。ただし、生じうるパフォーマンス上の問題が少々あります。最適なパフォーマンスを得るためには、裏で何が起こっているかを理解する必要があります。

賢明な選択をする: 「TArray を使用することが適切でしょうか?」

常にパフォーマンスを語る場合、 コードをプロファイリング して、問題がある適切なエリアに対処するようにします。初めから問題発生を回避する賢明な事前の対策を講じるのもよいでしょう。

コンテナ タイプ(TArray、TSet、TMap など) では、考慮すべき最も重要なことは多くの場合、クリティカル パスでのコードのオペレーションを考えて適切なコンテナを使用しているかどうかということです。例えば、固有の要素のリストを維持し、それに対して追加、削除、検索などを頻繁に行う必要がある場合、TArray を使用できますが、TSet の方がおそらくベターな選択でしょう。

コンパクトなメモリ空間で非常に高速な要素の繰り返しが必要ならば、TArray は優れた選択肢です。しかし、コードを記述しながら他のオペレーション (追加、削除など) が及ぼす影響に配慮する必要があります。ここでは、TArray の使用が適切であり、ゲームのシッピング時に貴重な最適化の時間を奪われないようにいくつかの簡単な方法について説明します。

1. 割り当てのポリシーに従い、デフォルトではTArray は、アイテムが追加されるにつれて配列が大きくなり、メモリを再割り当てします。

TArray に要素を追加することは簡単であり、TArray を非常に便利なものにしています。内部では、より多くのアイテムが追加されるに従い、より多くのメモリが定期的に割り当てられます。毎回そうするたびに、要素を新しい領域にコピーした後、古いメモリを解放しなければなりません。これは負荷が高いため、可能な限り回避したいところです。以下のように、パフォーマンス ヒットを回避するためにできることがあります。

1.1:追加するアイテム数、または上限がわかっていたら、メモリ領域を予め確保してください。

例えば、"Reserve" を呼び出す一行のコードを追加すると、この関数内でわずか一回の割り当てになるようにします。

// 配列の最後に文字の N 個のコピーを追加します。
void AppendCharacterCopies(char CharToCopy, int32 N, TArray<char>& Result)
{
	if (N > 0)
	{
		Result.Reserve(Result.Num() + N);
		for (int32 Index=0; Index < N; Index++)
		{
			Result.Add(CharToCopy);
		}
	}
}

通常、要素をひとつづつ N 回追加すると、配列が定期的に拡張するため途中で何回か再割り当てとコピーが行われます。しかし、ここでは現在の要素数を使って、Result が最低 N 個の追加要素を保持できるようにしてから追加を開始することで、一回の割り当てで済むようにします。既に十分余裕があれば、割り当てが起こる必要は全くありません!

1.2:関数パラメータとして使用される場合、TArrays を参照渡しにします。

1.1 の例で、TArray の参照を取るために関数がどのように宣言されているかに注意してください。代替の宣言を検討します。

// 配列を値渡しします!
void AppendCharacterCopies(char Original, int32 N, TArray<char> Result)

値渡しすることで、この関数の呼び出し元はまずその配列のコピーを作ってから、それをAppendCharacterCopies() に渡します。これ