列表与LT;>最好以最大容量初始化,只使用其中的一小部分,或初始化没有容量

时间:2010-01-22 20:25:32

标签: c# .net list

我有一个List<MyStruct>,我正在初始化为空,我将在解析数据时在循环中填充此结构。我知道将有一个最大可能的条目数将插入此列表中。现在让我们说1000.但是在我解析了1000个条目后,我最终可能只将2放入列表中。因此,我应该初始化容量为1000的List,或者不指定容量,只需添加少量条目。然而,它最终可能会增加所有1000个。性能明智的最佳方式是什么?

6 个答案:

答案 0 :(得分:20)

无关紧要。不要微观优化。只有在您有个好主意的情况下才设置容量,大致相当于您需要的数量。在引擎盖下,每次增长时列表都会增加一倍,因此增长的数量为O(log(n))。它应该非常有效。

答案 1 :(得分:9)

如果它真的可以广泛变化,那么你可能不想设置容量。对于大多数系列而言,容量在达到时会翻倍(我相信默认容量为16),因此当您填满时,您的容量将非常接近最大值。

答案 2 :(得分:3)

首先,您应该以最自然,可维护和可读的方式实现它。在这种情况下,只需创建一个新的List<T>(接受默认容量)并将对象添加到其中。如果您的应用程序不符合您的性能规范,那么您所做的就是对其进行分析。如果通过分析表明这是您的应用程序中的瓶颈,那么您尝试优化它。如果您的应用程序符合您的性能规范,或者此特定部分不是瓶颈,则忽略它。

其次,有时候实施细节很重要,而且情况就是如此。 List<T>实现的方式是一个动态可扩展的数组,以一定的容量开始,每次需要重新生成时,大小加倍。这意味着,如果您要将n对象添加到新创建的列表中,则会O(log n)重新生成,并且您最多会浪费O(n)个空间。除非你的系统内存紧张(也许你在手机上运行.NET CF),这不是什么大不了的事。从性能角度来看,对条目进行解析可能会比重新生成消耗更多的时间。因此,这也不太可能是一个因素。

答案 3 :(得分:1)

首先,我要说我不是在这样的地方写一个答案,我首先找到它,但我正在写一个,只是为了建议,也得到你的意见。

添加数据时列表的作用:

public void Add(T item) {
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size++] = item;
    _version++;
}

private void EnsureCapacity(int min) {
    if (_items.Length < min) {
        int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
        // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
        // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
        if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
        if (newCapacity < min) newCapacity = min;
        Capacity = newCapacity;
    }
}

看看这个,首先它确实完成了你们有些人所说的,它使容量加倍,而且与其他人不同的是,并且与Arrays的工作方式不同,当它达到提供的容量时它不会阻止用户

什么时候增加容量?在这一行:Capacity = newCapacity;;实际上它是执行操作的Capacity属性设置器:

public int Capacity {
    get {
        Contract.Ensures(Contract.Result<int>() >= 0);
        return _items.Length;
    }
    set {
        if (value < _size) {
            ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
        }
        Contract.EndContractBlock();

        if (value != _items.Length) {
            if (value > 0) {
                T[] newItems = new T[value];
                if (_size > 0) {
                    Array.Copy(_items, 0, newItems, 0, _size);
                }
                _items = newItems;
            }
            else {
                _items = _emptyArray;
            }
        }
    }
}

很明显,这不是一个简单的标志更改操作,只是让更多的项目进入,因为链接列表将做什么(说实话我总是把列表视为LinkedList'。现在我可以说与列表,我有更好的阅读性能并且写入性能较差(但我不确定我在说什么,有人确认我们是否应该在执行写入和一次读取操作时使用LinkedList ...))。因此,我们可以看到它创建一个新数组并将项目逐个复制到新列表...

所以这是我的建议:

  1. 正如@jason所说,当我们只能将一次写入操作运行到列表中时,我们不需要考虑传递一个值
  2. 如果列表的权重很小,例如只增加列表大小的几次迭代就不会做太多,例如每个人都说,如果它的2变为4,那么8 ......首先它只是三倍的时间我们增加了大小,其次,我们只复制了一些数据。无论代码放在何处,我们都可以忽略它,或者我希望如此。
  3. 但是,如果您从数据库中复制数千个数据,并且它从开始,2-> 4-&gt; 8-> 16-> 32-> 64-> 128- &GT; 256&GT; 512-&GT; 1024-&GT; 2048-&GT; ... 直到知道我们有10倍的时间增加数组大小,如果我们认为副本只是一个复制引用的操作,除了机器代码中需要完成的其他几件事,我们将有4094时间从一个复制数据数组到另一个,并且还占用了需要等待GC的空间的一半(在图形应用程序RAM中可能会成为问题,但是对我来说,编写示例太多了)... 因此,将此乘以同时调用此类代码的操作数,可能会显着降低性能。所以我可以考虑做以下事情:如果我知道一个数字,例如我知道我有x项,这些项可能引用0~2我可以考虑传递x或x * 2,它只会增长一次如果需要的话。 (请告诉我你的意见)。

  4. 在完成第3号想法时,每个列表的加倍似乎是合理的,无论你做什么,你只能提高一半的时间,并且完成整个操作只需要〜这两半中的两个,所以如果你不同时启动多个线程/任务,或者一个接一个地启动大量列表,你可以忽略它。

  5. 我也发现:private const int _defaultCapacity = 4;

    注意:如果您使用最大容量,正如它所说的那样,2G元素需要存储空间等于金额(正如它所说:// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.),那不是你希望初始化你的列表的数量,即使你的代码运行一次,它看起来像ram中的直接(线性/并行)数据太多(因为数据结构认为我们,如果C#没有做任何更新的事情)比我们的书中说的那样),分配也可能需要相当长的时间(我不知道这个过程)。所以我从不推荐它,如果你真的不知道需要多少,我想在这样的时候我们也应该考虑链表,如果数据真的是线性的,并且ram中可能有很多空间随机地方(如果是这种情况:在机器找到分配该空间的地方之前需要进行大量检查)。

答案 4 :(得分:0)

考虑到你的名单很小,你最好不要初始化它。它将使代码更容易阅读,而不会有任何明显的性能损失。

答案 5 :(得分:-1)

可能最好的办法就是妥协。将列表初始化为256。