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();
Quand je prends 4 résultats de stopwach1
et stopwach2
, stopwatch1
a toujours une valeur inférieure à stopwatch2
. Cela signifie que addrange
est toujours plus rapide que foreach
. Quelqu'un sait-il pourquoi?
AddRange
peut éventuellement vérifier si la valeur transmise implémente IList
ou IList<T>
. Si tel est le cas, il peut déterminer le nombre de valeurs comprises dans la plage, et donc la quantité d'espace qu'il doit allouer ... alors que la boucle foreach
peut avoir besoin de réaffecter plusieurs fois.
De plus, même après l'allocation, List<T>
peut utiliser IList<T>.CopyTo
pour effectuer une copie en bloc dans le tableau sous-jacent (pour les plages qui implémentent IList<T>
, bien sûr.)
J'imagine que vous constaterez que si vous essayez à nouveau votre test mais utilisez Enumerable.Range(0, 100000)
pour fillData
au lieu d'un List<T>
, les deux prendront à peu près le même temps.
Si vous utilisez Add
, le tableau interne est redimensionné progressivement, selon les besoins (en le doublant), à partir de la taille initiale par défaut de 10 (IIRC). Si tu utilises:
var manualFill = new List<int>(fillData.Count);
Je pense que ça va changer radicalement (plus de redimensionnement/copie de données).
À partir du réflecteur, AddRange
fait cela en interne, plutôt que de doubler:
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 ^^^
Parce que AddRange
vérifie la taille des éléments ajoutés et augmente la taille du tableau interne une seule fois.
Le désassemblage du réflecteur pour la méthode List AddRange a le code suivant
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;
}
}
Comme vous pouvez le constater, il existe des optimisations comme l’appel EnsureCapacity () et l’utilisation de Array.Copy ().
Lorsque vous utilisez AddRange
, la collection peut augmenter la taille du tableau une fois, puis y copier les valeurs.
À l'aide d'une instruction foreach
, la collection doit augmenter la taille de la collection plusieurs fois.
Augmenter la taille signifie copier le tableau complet, ce qui prend du temps.
C'est comme demander au serveur de vous apporter une bière dix fois et de lui apporter 10 bières à la fois.
Que pensez-vous est plus rapide :)
je suppose que ceci est le résultat de l'optimisation de l'allocation de mémoire . pour AddRange, la mémoire n'est allouée qu'une seule fois, et pendant que chaque répétition est réaffectée.
il peut aussi y avoir des optimisations dans l'implémentation AddRange (memcpy par exemple)
C’est parce que la boucle Foreach ajoutera toutes les valeurs que la boucle obtient une fois et
la méthode AddRange () rassemblera toutes les valeurs qu’elle obtient en tant que "morceau" et ajoutera ce morceau immédiatement à l’emplacement spécifié.
Tout simplement, c’est comme si vous aviez une liste de 10 articles à apporter du marché, ce qui serait plus rapide si vous apportiez tout cela un par un ou tout à la fois.
Essayez d’initialiser la capacité de la liste initiale avant d’ajouter manuellement des éléments:
var manualFill = new List<int>(fillData.Count);
L'extension AddRange ne parcourt pas chaque élément, mais applique chaque élément dans son ensemble. Foreach et .AddRange ont tous deux un but. Addrange va gagner le concours de vitesse pour votre situation actuelle.
Plus à ce sujet ici: