我有一个List<MyStruct>
,我正在初始化为空,我将在解析数据时在循环中填充此结构。我知道将有一个最大可能的条目数将插入此列表中。现在让我们说1000.但是在我解析了1000个条目后,我最终可能只将2放入列表中。因此,我应该初始化容量为1000的List,或者不指定容量,只需添加少量条目。然而,它最终可能会增加所有1000个。性能明智的最佳方式是什么?
答案 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 ...))。因此,我们可以看到它创建一个新数组并将项目逐个复制到新列表...
所以这是我的建议:
但是,如果您从数据库中复制数千个数据,并且它从开始,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,它只会增长一次如果需要的话。 (请告诉我你的意见)。
在完成第3号想法时,每个列表的加倍似乎是合理的,无论你做什么,你只能提高一半的时间,并且完成整个操作只需要〜这两半中的两个,所以如果你不同时启动多个线程/任务,或者一个接一个地启动大量列表,你可以忽略它。
我也发现: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。