C# 为什么 AddRange 比使用 foreach 循环更快?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9836471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 10:50:12  来源:igfitidea点击:

Why is AddRange faster than using a foreach loop?

c#.netc#-4.0

提问by HB MAAM

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();

When I take 4results from stopwach1and stopwach2, stopwatch1has always lower value than stopwatch2. That means addrangeis always faster than foreach. Does anyone know why?

当我从and取4 个结果时, 的值总是低于. 这意味着总是比 快。有谁知道为什么?stopwach1stopwach2stopwatch1stopwatch2addrangeforeach

采纳答案by Jon Skeet

Potentially, AddRangecan check where the value passed to it implements IListor IList<T>. If it does, it can find out how many values are in the range, and thus how much space it needs to allocate... whereas the foreachloop may need to reallocate several times.

潜在地,AddRange可以检查传递给它的值在哪里实现IListor IList<T>。如果是,它可以找出范围内有多少个值,因此需要分配多少空间……而foreach循环可能需要重新分配几次。

Additionally, even after allocation, List<T>can use IList<T>.CopyToto perform a bulk copy into the underlying array (for ranges which implement IList<T>, of course.)

此外,即使在分配之后,List<T>也可以用于IList<T>.CopyTo执行到底层数组的批量复制(IList<T>当然,对于实现 的范围。)

I suspect you'll find that if you try your test again but using Enumerable.Range(0, 100000)for fillDatainstead of a List<T>, the two will take about the same time.

我怀疑您会发现,如果您再次尝试测试但使用Enumerable.Range(0, 100000)forfillData而不是 a List<T>,则两者将花费大约相同的时间。

回答by Kirill Polishchuk

Because AddRangechecks size of added items and increases size of internal array only once.

因为AddRange检查添加项的大小并只增加内部数组的大小一次。

回答by Marc Gravell

If you are using Add, it is resizing the inner array gradually as needed (doubling), from the default starting size of 10 (IIRC). If you use:

如果您正在使用Add,它会根据需要逐渐调整内部数组的大小(加倍),从默认的起始大小 10 (IIRC) 开始。如果您使用:

var manualFill = new List<int>(fillData.Count);

I expect it'll change radically (no more resizes / data copy).

我希望它会发生根本性的变化(不再调整大小/数据复制)。

From reflector, AddRangedoes this internally, rather than growing in doubling:

从反射器,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 ^^^

回答by juergen d

When using AddRangethe Collection can increase the size of the array once and then copy the values into it.

使用AddRangeCollection 时可以增加一次数组的大小,然后将值复制到其中。

Using a foreachstatement the collection needs to increase size of the collection more than once.

使用foreach语句,集合需要不止一次增加集合的大小。

Increasing thr size means copying the complete array which takes time.

增加 thr 大小意味着复制整个数组需要时间。

回答by Spencer

The AddRange extension does not iterate through each item, but applies each item as a whole. Both foreach and .AddRange have a purpose. Addrange will win the contest of speed for your current situation.

AddRange 扩展不会遍历每个项目,而是将每个项目作为一个整体应用。foreach 和 .AddRange 都有一个目的。Addrange 将赢得您当前情况的速度竞赛。

回答by sll

Try out initialize intiial list capacity before manually adding items:

在手动添加项目之前尝试初始化初始列表容量:

var manualFill = new List<int>(fillData.Count); 

回答by ili

i suppose this is the result of optimisation of memory allocation. for AddRange memory allocates only once, and while foreach on each iteration reallocation is done.

我想这是优化内存分配的结果。for AddRange 内存只分配一次,而 foreach 则在每次迭代时重新分配。

also may be there are some optimisations in AddRange implementation (memcpy for example)

AddRange 实现中也可能有一些优化(例如 memcpy)

回答by Chamindu

The dissassembly from reflector for the List AddRange method has the following code

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;
    }
}

As you can see there are some optimizations like EnsureCapacity() call and using Array.Copy().

正如您所看到的,有一些优化,例如确保容量() 调用和使用 Array.Copy()。

回答by devdimi

This is like asking the waiter to bring you one beer ten times and asking him to bring you 10 beers at once.

这就像让服务员给你送一瓶啤酒十次,然后让他一次给你送十瓶啤酒。

What do you think is faster :)

你认为什么更快:)

回答by jungNitesh

It is because the Foreach loop will add all the values that the loop is getting one a time and
the AddRange() method will gather all the values it is getting as a "chunk" and add that chunk at once to the specified location.

这是因为 Foreach 循环将添加循环每次获得的所有值,而
AddRange() 方法将收集它作为“块”获得的所有值,并立即将该块添加到指定位置。

Simply understanding, it is just like you have a list of 10 items to bring from the market, which would be faster bringing all that one by one or all at single time.

简单理解,这就像你有一个清单,列出了要从市场上带来的 10 件物品,这样可以更快地将所有物品一件一件或全部带出来。