【问题标题】:Does List.Insert have any performance penalty?List.Insert 是否有任何性能损失?
【发布时间】:2013-09-03 08:15:21
【问题描述】:

给定一个列表:

List<object> SomeList = new List<object>();

在做什么:

SomeList.Insert(i, val);

对比

SomeList.Add(val);

有任何性能损失吗?如果是,它如何取决于:
- i - 插入索引
- SomeList.Count - 列表的大小

【问题讨论】:

  • Eric Lippert 在ericlippert.com/2012/12/17/performance-rant 中说 如果你有两匹马并且你想知道这两匹马中哪一个跑得更快,那就让你的马赛跑吧。
  • @Prash List&lt;T&gt; 是一个自动调整大小的数组包装器。它不是一个链表。
  • @Soner Gönül - 如果赢了,为什么重要?我认为是..我想知道为什么..
  • 这表明存在差异:dotnetperls.com/list-insert List Insert: 6770 ms List Add: 0 ms
  • 感谢@TheEvilPenguin 和 ChaosPandion。会做对的。将删除我的错误信息。

标签: c# list time-complexity


【解决方案1】:

List 类是 ArrayList 类的通用等价物。它使用一个数组来实现 IList 泛型接口,该数组的大小根据需要动态增加。

(source)

意味着内部数据存储为一个数组,因此很可能要执行insert它需要移动所有元素以腾出空间,因此它的复杂度是O(N),而@ 987654323@ 是(摊销的)常数时间 O(1) 操作,所以

总结 - 是的,它几乎总是会变慢,而且你的列表越大,它就会越慢。

【讨论】:

    【解决方案2】:

    如有疑问,请进行经验实验:

    List<object> SomeList = new List<object>();
    
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (var i = 0; i < 100000; i++)
        SomeList.Insert(0, String.Empty);
    sw.Stop();
    Console.WriteLine(sw.Elapsed.TotalMilliseconds);
    sw.Reset();
    
    SomeList = new List<object>();
    sw.Start();
    for (var i = 0; i < 100000; i++)
        SomeList.Add(String.Empty);
    sw.Stop();
    Console.WriteLine(sw.Elapsed.TotalMilliseconds);
    

    Insert 在我的机器上需要 2800 毫秒; Add 需要 0.8 毫秒。所以是的,Insert 的性能要差得多。

    【讨论】:

    • 我认为Insert(i, String.Empty) 更公平,因为这与添加的结果相同。 replit Insert 耗时 36 秒,Add 耗时 6 秒。
    • @NickGraham 但是Insert(i, String.Empty) 不会因为List&lt;&gt; 中的元素移动而导致性能损失。 OP 询问 Insert(i, o) 的性能如何受到插入索引和 List.Count 的影响,您的建议忽略了这些因素的影响。
    • 我想指出的是您正在执行 Insert(0, String.Empty)。这就是它表现不佳的原因。插入的性能较低,但也不是很多。我复制了您的测试,并添加了更多测试来证明容量,并且按索引插入可以提高性能。看看我发布的内容。此外,您应该先进行一次性测试。有时第一次运行会受到很小的打击,因此它可能会给您未来的实证实验带来误报。
    【解决方案3】:

    我知道这已经得到了彻底的回答,但我想指出,这些信息在 MSDN 文档中很容易获得。

    The documentation for List&lt;T&gt;.Insert() 状态:

    此方法是 O(n) 运算,其中 n 为 Count。

    The documentation for List&lt;T&gt;.Add() 状态:

    如果 Count 小于 Capacity,则此方法是 O(1) 操作。如果需要增加容量以容纳新元素,则此方法变为 O(n) 操作,其中 n 为 Count。

    如果您碰巧问了这个问题,因为您希望频繁地添加到列表的前 后,那么要使用的适当数据结构是 @987654323 @。

    Stephen Cleary 在这里提供了一个很好的 Deque 实现:http://nitodeque.codeplex.com/

    【讨论】:

    【解决方案4】:

    我的第一个想法是“是的,这会降低性能,因为Insert 需要将列表中的所有项目移到一个位置以便为新项目腾出空间”——但后来我更仔细地阅读了这个问题。所以:

    一般而言Insert 需要(可能很多)更多时间,因为它需要移动列表中已有的所有项目以为新项目腾出空间,所以它是 O(n ) 对列表长度的操作(如果列表已满,则还需要调整大小,但这仍然是 O(n) 操作)。另一方面,Add 只是简单地追加新项目,而无需移动任何内容,因此速度更快——O(1) 操作(除非列表需要调整大小,如上所示)。

    在这个具体示例中,如果列表一开始是空的,则不会有性能差异。

    当然,所有这些都没有实际意义,因为您应该根据自己的意图来选择方法,除非您有记录在案的性能问题。

    【讨论】:

      【解决方案5】:

      对于一个空列表,它没有什么不同。

      但是对于其他任何事情,它必须更慢*,因为要在前面插入,整个后备数组需要向右移动一个。

      *除非Add 导致Capacity 增加。

      【讨论】:

        【解决方案6】:

        当然可以。

        由于List&lt;T&gt; 由数组支持,任何要在列表开头插入的操作都必须移动(或复制)数组的所有其他元素。添加到列表末尾的操作将在恒定时间内发生,与列表中的项目数无关。

        【讨论】:

          【解决方案7】:

          我建立在 Pattermiester 的性能测试基础之上。当我想到插入时,我想到了一个数组,为了添加到一个数组中,您必须指定要在 Insert 处的索引。执行 Add(0, object) 似乎违反直觉。此外,当您进行性能测试时,总是首先创建一个扔掉的运行。有时需要的时间比实际应该的要长。

          结果

          2.282 - Throw Away Run
          2.6847 - List Insert By Index without Capacity
          10544.9766 - List Insert At Index 0 without Capacity
          1.8426 - List Add without Capacity
          2.0835 - List Insert By Index with Capacity
          1.4952 - List Add with Capacity
          9323.699 - List Insert at Index 0 with Capacity
          

          代码

                  var size = 200000;
                  //First test sometimes has a bad count
                  List<object> SomeList = new List<object>();
                  Stopwatch sw = new Stopwatch();
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Insert(i, String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds + " - Throw Away Run");
                  sw.Reset();
          
                  SomeList = new List<object>();
                  sw = new Stopwatch();
                  sw.Start();
                  for (var i = 0; i < size ; i++)
                      SomeList.Insert(i, String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds+" - List Insert By Index without Capacity");
                  sw.Reset();
          
                  SomeList = new List<object>();
                  sw = new Stopwatch();
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Insert(0, String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds + " - List Insert At Index 0 without Capacity");
                  sw.Reset();
          
                  SomeList = new List<object>();
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Add(String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds+" - List Add without Capacity");
                  sw.Reset();
          
                  SomeList = new List<object>(size);
                  sw = new Stopwatch();
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Insert(i, String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds+" - List Insert By Index with Capacity");
                  sw.Reset();
          
                  SomeList = new List<object>(size);
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Add(String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds+" - List Add with Capacity");
                  sw.Reset();
          
                  SomeList = new List<object>(size);
                  sw = new Stopwatch();
                  sw.Start();
                  for (var i = 0; i < size; i++)
                      SomeList.Insert(0, String.Empty);
                  sw.Stop();
                  Console.WriteLine(sw.Elapsed.TotalMilliseconds + " - List Insert at Index 0 with Capacity");
          

          所以看起来 Add 比 insert 性能要好一些。但是,如果您使用的是 Insert(0, ""),那么您肯定会扼杀性能。如果你想要最好的性能,那就是使用容量。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-04-23
            • 2011-04-06
            • 1970-01-01
            • 1970-01-01
            • 2015-03-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多