¿Por qué AddRange es más rápido que usar un bucle foreach?

var fillData = new List(); for (var i = 0; i < 100000; i++) { fillData.Add(i); } var stopwatch1 = new Stopwatch(); stopwatch1.Start(); var autoFill = new List(); autoFill.AddRange(fillData); stopwatch1.Stop(); var stopwatch2 = new Stopwatch(); stopwatch2.Start(); var manualFill = new List(); foreach (var i in fillData) { manualFill.Add(i); } stopwatch2.Stop(); 

Cuando tomo 4 resultados de stopwach1 y stopwach2 , el stopwatch1 siempre tiene un valor más bajo que el stopwatch2 . Eso significa que addrange es siempre más rápido que foreach . ¿Alguien sabe por qué?

Potencialmente, AddRange puede verificar dónde el valor que se le transfiere implementa IList o IList . Si lo hace, puede averiguar cuántos valores hay en el rango y, por lo tanto, cuánto espacio necesita asignar … mientras que el bucle foreach puede necesitar reasignar varias veces.

Además, incluso después de la asignación, List puede usar IList.CopyTo para realizar una copia masiva en la matriz subyacente (para los rangos que implementan IList , por supuesto).

Sospecho que encontrará que si prueba nuevamente pero usa Enumerable.Range(0, 100000) para fillData lugar de List , los dos tardarán aproximadamente el mismo tiempo.

Si está usando Add , está redimensionando la matriz interna gradualmente según sea necesario (doblando), desde el tamaño de inicio predeterminado de 10 (IIRC). Si utiliza:

 var manualFill = new List(fillData.Count); 

Espero que cambie radicalmente (no más cambios de tamaño / copia de datos).

Del reflector, AddRange hace esto internamente, en lugar de crecer en el doble:

 ICollection is2 = collection as ICollection; 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 ^^^ 

Porque AddRange verifica el tamaño de los elementos agregados y aumenta el tamaño de la matriz interna solo una vez.

El desassembly del reflector para el método List AddRange tiene el siguiente código

 ICollection is2 = collection as ICollection; 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; } } 

Como puede ver, hay algunas optimizaciones como la llamada a EnsureCapacity () y el uso de Array.Copy ().

Al utilizar AddRange Collection puede boost el tamaño de la matriz una vez y luego copiar los valores en ella.

Con una instrucción foreach , la colección necesita boost el tamaño de la colección más de una vez.

Aumentar el tamaño de thr significa copiar la matriz completa que lleva tiempo.

Esto es como pedirle al camarero que le traiga una cerveza diez veces y pidiéndole que le traiga 10 cervezas a la vez.

¿Qué crees que es más rápido 🙂

supongo que este es el resultado de la optimización de la asignación de memoria. para AddRange la memoria asigna solo una vez, y mientras foreach en cada iteración se realiza la reasignación.

También puede haber algunas optimizaciones en la implementación de AddRange (memcpy, por ejemplo)

Pruebe inicializar la capacidad de la lista inicial antes de agregar elementos manualmente:

 var manualFill = new List(fillData.Count); 

Es porque el bucle Foreach agregará todos los valores que el bucle está recibiendo uno por vez y
el método AddRange () recogerá todos los valores que obtiene como un “fragmento” y agregará ese fragmento de una vez a la ubicación especificada.

Simplemente entendiendo, es como si tuviera una lista de 10 elementos para traer del mercado, que sería más rápido traer todo eso uno por uno o todos a la vez.

La extensión AddRange no itera a través de cada elemento, sino que aplica cada elemento como un todo. Tanto foreach como .AddRange tienen un propósito. Addrange ganará el concurso de velocidad para su situación actual.

Más sobre esto aquí:

Addrange Vs Foreach