![](/img/trans.png)
[英]Why loop on array object with `foreach` is faster than lambda `ForEach`?
[英]Why is AddRange faster than using a foreach loop?
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();
当我从4个结果stopwach1
和stopwach2
, stopwatch1
比一直较低值stopwatch2
。 这意味着addrange
总是比foreach
快。 有谁知道为什么?
潜在地, AddRange
可以检查传递给它的值在哪里实现IList
或IList<T>
。 如果是,它可以找出范围内有多少个值,因此需要分配多少空间……而foreach
循环可能需要重新分配几次。
此外,即使在分配之后, List<T>
也可以使用IList<T>.CopyTo
向底层数组执行批量复制(当然,对于实现IList<T>
范围。)
我怀疑你会发现,如果你再次尝试你的测试,但使用Enumerable.Range(0, 100000)
作为fillData
而不是List<T>
,两者将花费大约相同的时间。
如果您使用的是Add
,它会根据需要逐渐调整内部数组的大小(加倍),从默认的起始大小 10 (IIRC) 开始。 如果您使用:
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;
}
}
正如你所看到的,有一些优化,比如确保容量()调用和使用 Array.Copy()。
使用AddRange
,集合可以增加一次数组的大小,然后将值复制到其中。
使用foreach
语句,集合需要不止一次增加集合的大小。
增加 thr 大小意味着复制整个数组需要时间。
这就像让服务员给你送一瓶啤酒十次,然后让他一次给你送十瓶啤酒。
你认为什么更快:)
我想这是优化内存分配的结果。 for AddRange 内存只分配一次,而 foreach 则在每次迭代时重新分配。
AddRange 实现中也可能有一些优化(例如 memcpy)
在手动添加项目之前尝试初始化初始列表容量:
var manualFill = new List<int>(fillData.Count);
这是因为 Foreach 循环会将循环每次获得的所有值相加,并且
AddRange() 方法将收集它作为“块”获取的所有值,并立即将该块添加到指定位置。
简单理解,这就像你有一个清单,列出了要从市场上带来的 10 件物品,这样可以更快地将所有物品一件一件或全部带出来。
AddRange 扩展不会遍历每个项目,而是将每个项目作为一个整体应用。 foreach 和 .AddRange 都有一个目的。 Addrange 将赢得您当前情况的速度竞赛。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.