![](/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.