var fillData = new List<int>();
for (var i = 0; i < 100000; i++)
{
fillData.Add(i);
}
var stopwatch1 = new Stopwatch();
stopwatch1.Start();
var autoFill = new List<int>();
autoFill.AddRange(fillData);
stopwatch1.Stop();
var stopwatch2 = new Stopwatch();
stopwatch2.Start();
var manualFill = new List<int>();
foreach (var i in fillData)
{
manualFill.Add(i);
}
stopwatch2.Stop();
4stopwach1
およびstopwach2
の結果を取得すると、stopwatch1
の値は常にstopwatch2
よりも低くなります。つまり、addrange
はforeach
よりも常に高速です。誰が理由を知っていますか?
潜在的に、AddRange
は、渡された値がIList
または_IList<T>
_を実装する場所を確認できます。存在する場合、範囲内にある値の数、したがって割り当てる必要があるスペースの量を調べることができます... foreach
ループは数回再割り当てする必要がある場合があります。
さらに、割り当て後でも、_List<T>
_は _IList<T>.CopyTo
_ を使用して、基礎となる配列への一括コピーを実行できます(もちろん_IList<T>
_を実装する範囲の場合)。
もう一度テストを試して、_List<T>
_の代わりにfillData
にEnumerable.Range(0, 100000)
を使用すると、2つはほぼ同じ時間がかかると思います。
Add
を使用している場合、デフォルトの開始サイズ10(IIRC)から内部配列のサイズを必要に応じて(2倍に)徐々に変更しています。使用する場合:
var manualFill = new List<int>(fillData.Count);
私はそれが根本的に変わることを期待しています(これ以上のサイズ変更/データコピーはありません)。
リフレクターから、AddRange
は倍増するのではなく、内部的にこれを行います。
ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
int count = is2.Count;
if (count > 0)
{
this.EnsureCapacity(this._size + count);
// ^^^ this the key bit, and prevents slow growth when possible ^^^
AddRange
は追加されたアイテムのサイズをチェックし、内部配列のサイズを一度だけ増加させるためです。
List AddRangeメソッドのリフレクターからの逆アセンブリには、次のコードがあります
ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
int count = is2.Count;
if (count > 0)
{
this.EnsureCapacity(this._size + count);
if (index < this._size)
{
Array.Copy(this._items, index, this._items, index + count, this._size - index);
}
if (this == is2)
{
Array.Copy(this._items, 0, this._items, index, index);
Array.Copy(this._items, (int) (index + count), this._items, (int) (index * 2), (int) (this._size - index));
}
else
{
T[] array = new T[count];
is2.CopyTo(array, 0);
array.CopyTo(this._items, index);
}
this._size += count;
}
}
ご覧のとおり、EnsureCapacity()呼び出しやArray.Copy()の使用などの最適化がいくつかあります。
AddRange
を使用する場合、コレクションは配列のサイズを一度増やしてから、値をそこにコピーできます。
foreach
ステートメントを使用して、コレクションはコレクションのサイズを複数回増やす必要があります。
Thrサイズを大きくすると、配列全体をコピーするのに時間がかかります。
これは、ウェイターにビールを1杯持って行くように10回頼み、ビールを一度に10杯持ってくるように頼むようなものです。
あなたはより速いと思います:)
これはメモリ割り当ての最適化の結果だと思います。 AddRangeの場合、メモリは1回のみ割り当てられ、各反復でforeachが再割り当てされます。
また、AddRangeの実装にいくつかの最適化があるかもしれません(たとえば、memcpy)
これは、Foreachループがすべての値を追加して、ループが1つずつ取得しているためです。
AddRange()メソッドは、「チャンク」として取得するすべての値を収集し、指定された場所にそのチャンクを一度に追加します。
簡単に理解すると、市場から10個のアイテムのリストを持っているようなものです。これにより、すべてのアイテムを1つずつ、またはすべてを一度にすばやく取得できます。
アイテムを手動で追加する前に、初期リストの容量を初期化してみてください。
var manualFill = new List<int>(fillData.Count);
AddRange拡張機能は各アイテムを反復処理しませんが、各アイテムを全体として適用します。 foreachと.AddRangeの両方に目的があります。 Addrangeはあなたの現在の状況でスピードのコンテストに勝ちます。
詳細はこちら: