.NETでメモリの解放を高速化する方法があるかどうかを考えていました。.NET(マネージコードのみ)でゲームを作成していますが、重要なグラフィックは必要ありませんが、パフォーマンスを損なうことのないように適切に記述したいと思います。
たとえば、不要になったオブジェクトにnull値を割り当てると便利ですか?これはインターネット上のいくつかのサンプルで見られます。
.NETでメモリの解放を高速化する方法があるかどうかを考えていました。.NET(マネージコードのみ)でゲームを作成していますが、重要なグラフィックは必要ありませんが、パフォーマンスを損なうことのないように適切に記述したいと思います。
たとえば、不要になったオブジェクトにnull値を割り当てると便利ですか?これはインターネット上のいくつかのサンプルで見られます。
不要になったオブジェクトに null 値を割り当てると便利ですか?
一般的に、いいえ。オンラインでこれを行うサンプルのほとんどは、これが一般的なベスト プラクティスであった VB6 から .Net に移行した人々によるものです。.Net では、あまり役に立ちません。実行時間が非常に長いメソッドがある場合、ガベージ コレクターがオブジェクトを少し早く見つけられる可能性がありますが、メソッドがそれほど長い場合は、別の問題が発生します。
代わりに、.Net では、短いメソッドを作成し、変数を可能な限り小さいスコープ ブロックでできるだけ遅く定義する必要があります。スコープによって決定される変数の「自然な」寿命を使用しますが、その自然な寿命は短くしてください。
ここで少なくとも1つの他の回答で示唆されているように、独自のガベージコレクションを行うべきではありません。これにより、実際には動作が遅くなる可能性があります。.Net は世代別ガベージ コレクタを使用します。ガベージ コレクションを強制することで、ターゲット オブジェクトを収集できます(そうでない場合もあります。ガベージ コレクションには保証がありません)。ただし、まだ収集できない他のオブジェクトの束をより高い順序の世代に格納するように強制することもあり、将来収集するのが難しくなります。だから、これをしないでください。
心配しすぎたり、時期尚早に最適化したりしないでください。.NET のメモリ管理は自動で非常に効率的です。「最適化」を開始する場合は、自分が何をしているのかを正確に知る必要があります。そうしないと、速度が低下する可能性があります。
したがって、まずゲームを終了してから、遅すぎる場合はプロファイラーを使用して問題を見つけてください。ほとんどの場合、メモリの問題ではありません。
.net のグラフィックス関連オブジェクト (Image とその子孫、Graphics、Pen、Brush など) のほとんどは、IDisposable を実装しています。特定の操作にオブジェクトを使用する場合は、次のパターンを使用してください。
using(var g = Graphics.FromBitmap(bmp))
{
//Do some stuff with the graphics object
}
このようにすることで、管理されていないリソースが範囲外になったときに確実に解放されます。
.netでのオブジェクトの割り当ては、パフォーマンスに最も影響を与えるものの1つです。これを回避するために、使用したオブジェクトをリサイクルするファクトリパターンを使用します。簡単な一般的な実装は次のとおりです。
internal class ListFactory<T>
where T: IRecyclable, new()
{
private List<T> _internalList;
private int _pointer;
public ListFactory()
{
_internalList = new List<T>();
_pointer = 0;
}
public void Clear()
{
_pointer = 0;
}
public int Count
{
get
{
return _pointer;
}
}
public List<T> InnerList
{
get
{
return _internalList;
}
}
//Either return T form the list or add a new one
//Clear T when it is being reused
public T Create()
{
T t;
//If the pointer is less than the object count then return a recycled object
//Else return a new object
if (_pointer < _internalList.Count )
{
t = _internalList[_pointer];
t.Recycle();
}
else
{
t = new T();
_internalList.Add(t);
}
_pointer ++;
return t;
}
}
私のラインルーティングアルゴリズムでは、次のインターフェイスを実装するRouteNodeとして常に多くの値を保持する必要があります。
public interface IRecyclable
{
void Recycle();
}
これらは絶えず作成され、破壊されます。これらのオブジェクトをリサイクルするには、新しいファクトリを作成します。
nodeFactory = new ListFactory<RouteNode>();
オブジェクトが必要な場合は、createメソッドを呼び出します。
RouteNode start = nodeFactory.Create();
RouteNode goal = nodeFactory.Create();
リスト内のオブジェクトの処理が終了したら、リストをクリアします。ポインタはリストの先頭にリセットされますが、オブジェクト自体は破棄されません。実際、Recycleメソッドは、オブジェクトが再度回収されたときにのみ呼び出されます。(他のオブジェクト参照がある場合は、これを早く実行することをお勧めします。以下のコメントを参照してください)
これはかなり単純な実装ですが、開始する場所です。
不要になったオブジェクトを null に設定すると便利な場合があります。多くの場合、それは役に立たないか逆効果ですが、常にではありません。役立つ 4 つの主な状況:
これを行うには (50 MB を超える頻繁な割り当てでこれを行う必要がありました)、次のように呼び出します。
myObj = null;
GC.Collect();
GC.WaitForPendingFinalizers();
アプリのメモリ使用量が大幅に減少することに気付きました。理論的には、これを行う必要はありません。ただし、実際には、32 ビット Windows OS では、常に 300 MB を超える 2 つの連続したブロックを解放する可能性があり、そのスペースが多数の小さな割り当てまたは一連の大きな割り当てによって占められていると、他の大きな割り当てが発生する可能性があります。不必要に失敗する。ガベージ コレクターは可能な場合はバックグラウンドで実行されますが、どうしても今すぐ大量の割り当てを行わなければならない場合は、この一連の行がそれを可能にします。
編集:私がコメントに入れたものから、反対票を投じた人のために。
Rico Mariani によるガベージ コレクションに関する投稿全体を読むと、大規模で、頻度が低く、予測不可能なメモリ割り当てがシナリオ 2 に該当することがわかります。具体的には:
ルール 2
繰り返し発生しないイベントが発生したばかりで、このイベントによって多くの古いオブジェクトが消滅する可能性が高い場合は、GC.Collect() の呼び出しを検討してください。
この典型的な例は、クライアント アプリケーションを作成していて、大量のデータが関連付けられた非常に大きく複雑なフォームを表示する場合です。ユーザーがこのフォームを操作したところ、いくつかの大きなオブジェクトが作成される可能性があります... XML ドキュメント、または大きな DataSet や 2 つなどです。フォームが閉じられると、これらのオブジェクトは無効になるため、GC.Collect() はそれらに関連付けられたメモリを再利用します。
では、コレクターに電話する可能性のある時期として、なぜこれを提案するのでしょうか? つまり、私の通常のアドバイスは、「コレクターは自動調整なので、いじらないでください」のようなものです。あなたが尋ねるかもしれない態度の変化はなぜですか?
ここでは、過去に基づいて未来を予測しようとするコレクターの傾向[原文のまま]が失敗する可能性が高い状況です。
ゲームで大量の割り当てを行う場合は、メモリの処理方法に注意する必要があります。ガベージ コレクターは、過去のイベントに基づいて予測を行います。32 ビット マシン上の大きなメモリ ブロックは、適切に管理されていないと、将来の割り当てに壊滅的な影響を与える可能性があります。まだ行っていない場合でも、私が間違っていると自動的に思い込まないでください。実行したことがある場合は、適切に実行する方法の説明を歓迎します (つまり、メモリをデフラグして、特定の時間に常に 50 ~ 100 MB のメモリを割り当てることができるようにする方法)。