私のコードには、数十または数百の要素を持つ一般的なリストがいくつかあります。Clear()
時々、このリストを他のオブジェクトで補充する必要があるので、問題は、メソッドを呼び出すのとnew List<T>()
?
8 に答える
Clear()
メソッドを呼び出すか、`new List() を作成するか、どちらが速くなりますか?
これは答えられません。コレクションの存続期間など、多くの要因に大きく左右されます。
ここでの最良のオプションは次のとおりです。
アプリケーションのプロファイリングを行い、これが本当に重要かどうかを確認してください。知覚できるほどの違いはないと思われますが、その場合は、このオブジェクトをどのように考えるかという点で最も理にかなった方法を使用します。
問題がある場合は、両方のコード セットを記述し、速度の違いを測定します (存在する場合)。
実用的な観点からは、リストの容量を縮小せず、リストに含まれる値を削除するだけなので、呼び出しClear()
によって実際にメモリ (それ自体で使用される) が減少することはありません。List<T>
新しいリストを作成するList<T>
と、新しいリストが割り当てられ、その結果、成長に伴いより多くの割り当てが発生します。
ただし、これは遅くなるという意味ではありません。多くの場合、大きな配列をより高いガベージ コレクション世代に昇格させる可能性が低くなり、GC プロセスをより高速に保つことができるため、再割り当てはより高速になります。
正確なシナリオを知り、プロファイラーで測定しない限り、シナリオでどちらが優れているかを知る方法はありません。
私はこのテストを実行しました:
private static void Main(string[] args)
{
int defaultN = 1000;
Stopwatch sw = new Stopwatch();
while (true)
{
Console.WriteLine("Enter test elements number:");
int n;
if (!int.TryParse(Console.ReadLine(), out n)) n = defaultN;
else defaultN = n;
Console.WriteLine($"Test with {n} elements");
List<object> list = Enumerable.Repeat(new object(), n).ToList();
sw.Start();
Clear(list);
sw.Stop();
Console.WriteLine("Clear: {0} ms", sw.ElapsedTicks / 10000D);
GC.Collect();
GC.WaitForPendingFinalizers();
List<object> list2 = Enumerable.Repeat(new object(), n).ToList();
sw.Restart();
Reinitialize(list2);
sw.Stop();
Console.WriteLine("Reinitialize: {0} ms", sw.ElapsedTicks / 10000D);
GC.Collect();
GC.WaitForPendingFinalizers();
List<object> list3 = Enumerable.Repeat(new object(), n).ToList();
sw.Restart();
ReinitializeAndCollect(list3);
sw.Stop();
Console.WriteLine("ReinitializeAndCollect: {0} ms", sw.ElapsedTicks / 10000D);
Console.WriteLine("===");
}
}
private static List<object> Clear(List<object> list)
{
list.Clear();
return list;
}
private static List<object> Reinitialize(List<object> list) => new List<object>();
private static List<object> ReinitializeAndCollect(List<object> list)
{
list = new List<object>();
GC.Collect();
GC.WaitForPendingFinalizers();
return list;
}
私の通常のコア i3 プロセッサの結果に基づく私の結論:
数千の要素の場合 - リストをクリアすることをお勧めします。高速でメモリ効率が良いです。
コレクションに 100,000 を超える要素がある場合、再初期化がより魅力的になります。プロファイリングの後で、ここにボトルネックがあると思われる場合は、それを使用してください。再初期化は非常に高速ですが、3 番目の方法のテストが示すように、将来のガベージ コレクションはリストをクリアするのと同じくらい遅くなります。
簡単に言えば、アプリケーションをプロファイリングしていない場合は、Clear
. オブジェクトの再利用は良いことです。もしそうなら、あなたはすでに何をすべきかを知っています。
これは多くの要因に依存しますが、長い目で見れば、おそらくあなたのプログラムでは問題にならないでしょう (カウントするのに十分です)。
msdnドキュメント .Clear()
からは、O(n) 操作です。
新しいインスタンスの初期化には、独自のオーバーヘッドと (コレクションを同じ長さに保つ場合、O(n) 操作、つまり nAdd()
呼び出し) があります。
実際にこれをテストする唯一の方法は、プログラムにいくつかのストップウォッチを設定し、その価値があると本当に考える場合にどのような効果があるかを確認することです。おそらく; それはそれだけの価値はありません。
私の考えでは、すでにコレクションを作成している場合、それが、そもそもメソッドClear()
がある理由です。Clear()
これはイライラするかもしれませんが、答えは、それは問題ではないということです。この 2 つの時間差は非常に小さいため、おそらくアプリケーションには何の違いもありません。よりクリーンでわかりやすいコードにつながることを行い、マイクロ最適化のためのプログラムを作成しないようにします。
Clear()
すべての要素を削除し、既存の容量を維持しますが、新しいリストを作成するには、マネージ ヒープから少なくとも 1 つの割り当てが必要です (初期容量が小さい場合、項目が追加されるため、より多くの割り当てが必要になる可能性があります)。
多数のアイテムがあり、アイテムの数が各反復でほぼ同じである場合、を使用した
Clear
方がわずかに高速になる可能性があります。1 回の繰り返しで非常に多くの項目があり、その後の繰り返しではるかに少ない数の
Clear
場合、不必要に大きな容量のリストをメモリに保持することになるため、使用するコストが高くなる可能性があります。
もちろん、多くの (ほとんどの?) シナリオでは、違いはごくわずかです。
私は自分自身のためにいくつかのテストを行いました。結果 (速度) は次のとおりです。
- 小さいリストの場合 - たとえば 3 つの項目の場合、新しいリストを作成する方が高速ですが、違いは大きくありません
- 平均して 10 個以上のアイテムの場合は、リストをクリアすることをお勧めします。値型の場合ははるかに優れており (例: 3 ~ 4 倍)、値型の場合は 20% 優れています。
しかし、最終的には、アプリケーションをプロファイリングして、アプリケーション全体のボトルネックを見つけたほうがよいでしょう。
ここで何か根本的な間違いをしているのかもしれませんが、C# で ASP.NET アプリケーションを開発しているときに、Clear() と new を使用するとかなりの違いに遭遇します。データ系列を持つグラフを含む統計ページを作成しています。グラフごとに、これを行うセクションがあります。
chart = new ChartistChart() { Title = "My fancy chart" };
series = new List<ChartistMetaValue>();
*some code for getting the statistics*
chart.Series.Add(series);
chartistLineCharts.Add(chart);
その後、別のチャートが続きます。
chart = new ChartistChart() { Title = "My second fancy chart" };
series = new List<ChartistMetaValue>();
*some code for getting the statistics*
chart.Series.Add(series);
chartistLineCharts.Add(chart);
これは、series
で再割り当てされても問題なく機能new
しますが、そうすると
series.Clear();
chart.Series
代わりに、内部のエントリを実際にクリアするchartistLineCharts
ため、統計ページは最後のチャートの系列のみを取得することになります。ここにはメモリ ポインタのような何らかのリンクがあると思いますが、これは最初に説明したものとは別の問題ですが、new
少なくともClear()
. 回避方法はあるのかもしれませんが。
オブジェクトが値型の場合、Clear() を使用してメモリの将来の割り当てを減らします。それ以外の場合、両方のアプローチはほとんど同じです。