簡體   English   中英

為什么 AddRange 比使用 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個結果stopwach1stopwach2stopwatch1比一直較低值stopwatch2 這意味着addrange總是比foreach快。 有誰知道為什么?

潛在地, AddRange可以檢查傳遞給它的值在哪里實現IListIList<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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM