c# make Warum ist AddRange schneller als eine foreach-Schleife?




string for each c# (8)

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();

Wenn ich 4 Ergebnisse von stopwach1 und stopwach2 , hat stopwatch1 immer einen niedrigeren Wert als stopwatch2 . Das bedeutet, dass der addrange immer schneller ist als foreach . Weiß jemand warum?


Die Disassemblierung von Reflector für die List-AddRange-Methode hat den folgenden Code

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;
    }
}

Wie Sie sehen können, gibt es einige Optimierungen wie den Aufruf EnsureCapacity () und die Verwendung von Array.Copy ().


Das ist so, als würde man den Kellner bitten, Ihnen zehn Mal ein Bier zu bringen und ihn zu bitten, Ihnen 10 Bier auf einmal zu bringen.

Was denkst du ist schneller :)


Dies liegt daran, dass die Foreach-Schleife alle Werte hinzufügt, die die Schleife einmal erhält
Die AddRange () -Methode sammelt alle Werte, die sie erhält, als "Chunk" und fügt diesen Chunk gleichzeitig zum angegebenen Speicherort hinzu.

Einfach zu verstehen, es ist genau so, als ob Sie eine Liste von 10 Produkten vom Markt haben, die schneller alles eins nach dem anderen oder alle auf einmal bringen würden.


Versuchen Sie, die anfängliche Listenkapazität zu initialisieren, bevor Sie manuell Elemente hinzufügen:

var manualFill = new List<int>(fillData.Count); 

Weil AddRange Größe der hinzugefügten Elemente überprüft und die Größe des internen Arrays nur einmal erhöht.


Ich nehme an, dies ist das Ergebnis der Optimierung der Speicherzuweisung. für AddRange Speicher reserviert nur einmal, und während foreach bei jeder Iteration Reallokation erfolgt.

möglicherweise gibt es auch einige Optimierungen in der AddRange-Implementierung (zum Beispiel memcpy)


Wenn Sie AddRange die Collection die Größe des Arrays einmal erhöhen und dann die Werte AddRange .

Mit einer foreach Anweisung muss die Sammlung die Größe der Sammlung mehr als einmal erhöhen.

Eine Erhöhung der Größe bedeutet, dass das komplette Array kopiert werden muss, was Zeit benötigt.


Wenn Sie Add , ändert es die Größe des inneren Arrays nach Bedarf (Verdopplung) von der Standardstartgröße von 10 (IIRC). Wenn du benutzt:

var manualFill = new List<int>(fillData.Count);

Ich erwarte, dass sich das radikal ändert (keine Größenanpassungen / Datenkopien mehr).

Von Reflector AddRange tut AddRange dies intern, anstatt AddRange wachsen:

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 ^^^




c#-4.0