it-swarm.com.de

Warum ist AddRange schneller als eine foreach-Schleife?

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 aus stopwach1 und stopwach2 nehme, hat stopwatch1 immer einen niedrigeren Wert als stopwatch2. Das heißt, addrange ist immer schneller als foreach. Weiß jemand, warum?

53
HB MAAM

Möglicherweise kann AddRange prüfen, wo der an sie übergebene Wert IList oder IList<T> implementiert. Wenn dies der Fall ist, kann es herausfinden, wie viele Werte im Bereich liegen und somit wie viel Speicherplatz zur Zuteilung erforderlich ist ..., während die foreach-Schleife möglicherweise mehrere Male neu zugeteilt werden muss.

Außerdem kann List<T> auch nach der Zuweisung IList<T>.CopyTo verwenden, um eine Massenkopie in das zugrunde liegende Array (für Bereiche, die natürlich IList<T> implementieren) auszuführen.

Ich vermute, Sie werden feststellen, dass, wenn Sie den Test erneut versuchen, aber Enumerable.Range(0, 100000) für fillData anstelle von List<T> verwenden, die beiden ungefähr dieselbe Zeit benötigen.

81
Jon Skeet

Wenn Sie Add verwenden, ändert sich die Größe des inneren Arrays stufenweise (Verdopplung), und zwar von der Standardgröße von 10 (IIRC). Wenn du benutzt:

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

Ich gehe davon aus, dass sich das radikal ändert (keine Größenänderung/Datenkopie mehr).

Vom Reflektor aus macht AddRange dies intern, anstatt sich zu verdoppeln:

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 ^^^
55
Marc Gravell

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

17

Die Demontage von Reflektor 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, gibt es einige Optimierungen wie den Aufruf von EnsureCapacity () und die Verwendung von Array.Copy ().

6
Chamindu

Bei Verwendung von AddRange kann die Collection das Array einmal vergrößern und dann die Werte in das Array kopieren.

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

Wenn Sie die thr-Größe erhöhen, wird das gesamte Array kopiert, was Zeit kostet.

5
juergen d

Dies ist, als würde man den Kellner bitten, dir zehnmal ein Bier zu bringen und ihn zu bitten, dir 10 Bier auf einmal zu bringen. 

Was glaubst du ist schneller :)

4
devdimi

ich nehme an, dies ist das Ergebnis einer Optimierung der Speicherzuordnung. Für AddRange wird Speicher nur einmalig zugewiesen, und während für jede Iteration eine erneute Zuweisung erfolgt.

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

2
ili

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

Einfach zu verstehen, es ist genau so, als hätten Sie eine Liste von 10 Artikeln, die Sie vom Markt mitbringen könnten, was schneller sein würde, wenn Sie das alles einzeln oder alle zur gleichen Zeit bringen würden.

1
jungNitesh

Testen Sie die initiale Listenkapazität, bevor Sie Elemente manuell hinzufügen:

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

Die AddRange-Erweiterung durchläuft nicht jedes Element, sondern wendet jedes Element als Ganzes an. Foreach und .AddRange haben einen Zweck. Addrange gewinnt den Geschwindigkeitswettbewerb für Ihre aktuelle Situation.

Mehr dazu hier:

Addrange Vs Foreach

0
Spencer