调用ToList()会对性能产生影响吗?

时间:2013-03-20 06:06:47

标签: c# arrays performance list

使用ToList()时,是否需要考虑性能影响?

我正在编写一个查询来从目录中检索文件,这是查询:

string[] imageArray = Directory.GetFiles(directory);

但是,由于我喜欢与List<>合作,我决定投入......

List<string> imageList = Directory.GetFiles(directory).ToList();

那么,在决定进行这样的转换时是否应该考虑某种性能影响 - 或者只是在处理大量文件时要考虑?这是一个微不足道的转换吗?

8 个答案:

答案 0 :(得分:152)

IEnumerable.ToList()

是的,IEnumerable<T>.ToList()确实会对性能产生影响,它是 O(n)操作,但在性能关键操作中可能只需要注意。

ToList()操作将使用List(IEnumerable<T> collection)构造函数。这个构造函数必须复制数组(更一般地说是IEnumerable<T>),否则原始数组的未来修改也会在源T[]上发生变化,这通常是不可取的。

我想重申一下这只会对一个巨大的列表产生影响,复制内存块是一个非常快速的操作。

方便提示,As vs To

您会注意到在LINQ中有几种方法以As开头(例如AsEnumerable())和To(例如ToList())。以To开头的方法需要像上面那样的转换(即可能会影响性能),而以As开头的方法则不需要,只需要一些强制转换或简单操作。

List<T>

的其他详细信息

以下是List<T>如果你感兴趣的话如何运作的更多细节:)

List<T>还使用称为动态数组的构造,需要根据需要调整大小,此resize事件将旧数组的内容复制到新数组。所以它从小increases in size if required开始。

这是CapacityCountList<T>属性之间的差异。 Capacity指的是幕后数组的大小,CountList<T>中始终为<= Capacity的项目数。因此,当项目添加到列表中时,将其增加到Capacity之后,List<T>的大小会加倍,并且数组会被复制。

答案 1 :(得分:33)

  

调用toList()会对性能产生影响吗?

当然可以。从理论上讲,即使i++会对性能产生影响,也会使程序放慢几个小时。

.ToList做什么?

当您调用.ToList时,代码会调用Enumerable.ToList(),这是return new List<TSource>(source)的扩展方法。在相应的构造函数中,在最糟糕的情况下,它通过item容器并逐个添加到一个新容器中。所以它的行为对性能的影响很小。要成为应用程序的性能瓶颈是不可能的。

问题中的代码有什么问题

Directory.GetFiles遍历文件夹并将所有文件的名称​​立即返回到内存中,它可能会导致字符串[]花费大量内存,从而减慢所有内容。< / p>

应该做什么

这取决于。如果您(以及您的业务逻辑)保证文件夹中的文件数量总是很小,则代码是可接受的。但仍然建议在C#4中使用惰性版本Directory.EnumerateFiles。这更像是一个查询,不会立即执行,您可以在其上添加更多查询,如:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

一旦找到名称中包含“myfile”的文件,就会停止搜索路径。这显然比.GetFiles具有更好的性能。

答案 2 :(得分:14)

  

调用toList()会对性能产生影响吗?

是的。使用扩展方法Enumerable.ToList()将从List<T>源集合构造一个新的IEnumerable<T>对象,这当然会对性能产生影响。

但是,了解List<T>可能有助于您确定效果影响是否显着。

List<T>使用数组(T[])来存储列表的元素。分配后,无法扩展数组,因此List<T>将使用超大数组来存储列表的元素。当List<T>超出底层数组的大小时,必须分配一个新数组,并且在列表增长之前必须将旧数组的内容复制到新的更大数组。

List<T>构建新的IEnumerable<T>时,有两种情况:

  1. 源集合实现ICollection<T>:然后ICollection<T>.Count用于获取源集合的确切大小,并在将源集合的所有元素复制到之前分配匹配的后备数组使用ICollection<T>.CopyTo()的支持数组。此操作非常有效,可能会映射到某些CPU指令以复制内存块。但是,就性能而言,新阵列需要内存,复制所有元素需要CPU周期。

  2. 否则,源集合的大小未知,IEnumerable<T>的枚举器用于将每个源元素一次添加到新List<T>。最初,后备数组为空,并创建一个大小为4的数组。然后,当这个数组太小时,大小加倍,因此后备数组增长如此4,8,16,32等。每当后备数组增长时,必须重新分配它,并且必须复制到目前为止存储的所有元素。与第一种可以立即创建正确大小的数组的情况相比,此操作成本更高。

    此外,如果你的源集合包含33个元素,那么列表将最终使用64个元素的数组,浪费一些内存。

  3. 在您的情况下,源集合是一个实现ICollection<T>的数组,因此除非您的源数组非常大,否则不应该关注性能影响。调用ToList()只会复制源数组并将其包装在List<T>对象中。对于小型收藏品来说,即使第二种情况的表现也不值得担心。

答案 3 :(得分:4)

ToList()创建一个新的List并将元素放入其中,这意味着执行ToList()会产生相关的成本。如果收集量很小,那么成本不会很明显,但是如果使用ToList,拥有庞大的集合可能会导致性能下降。

一般情况下,除非将集合转换为List,否则不应使用ToList()。例如,如果您只想迭代集合,则无需执行ToList

如果您正在使用LINQ to SQL对数据源(例如数据库)执行查询,那么执行ToList的成本要高得多,因为当您使用带有LINQ to SQL的ToList而不是执行延迟执行时,即在需要时加载项目(它可以在许多情况下有用)它立即将数据库中的项目加载到内存中

答案 4 :(得分:4)

  

“是否需要考虑性能影响?”

您的确切方案的问题在于,首先您对性能的真正关注将来自硬盘驱动器的速度和驱动器缓存的效率。

从这个角度来看,影响肯定可以忽略不计, NO 不需要考虑。

但是,如果您真的需要List<>结构的功能,可能会提高您的工作效率,或者您的算法更友好,或者其他一些优势。否则,你只是故意添加一个无关紧要的性能,无缘无故。在这种情况下,你自然不应该这样做! :)

答案 5 :(得分:3)

它将像(in)一样有效:

var list = new List<T>(items);

如果你反汇编带有IEnumerable<T>的构造函数的源代码,你会发现它会做一些事情:

  • 致电collection.Count,如果collectionIEnumerable<T>,则会强行执行。如果collection是数组,列表等,则应为O(1)

  • 如果collection实施ICollection<T>,则会使用ICollection<T>.CopyTo方法将项​​目保存在内部数组中。 O(n),为n集合的长度。

  • 如果collection未实现ICollection<T>,它将遍历集合中的项目,并将其添加到内部列表中。

所以,是的,它将消耗更多内存,因为它必须创建一个新列表,而在最坏的情况下,它将是O(n) ,因为它将遍历collection制作每个元素的副本。

答案 6 :(得分:2)

考虑到检索文件列表的性能,ToList()可以忽略不计。但其他情况并非如此。这实际上取决于你使用它的位置。

  • 调用数组,列表或其他集合时,可以将集合的副本创建为List<T>。此处的性能取决于列表的大小。你应该在必要时这样做。

    在您的示例中,您在数组上调用它。它遍历数组并将项逐个添加到新创建的列表中。因此,性能影响取决于文件数量。

  • 在呼叫IEnumerable<T>时,您具体化 IEnumerable<T>(通常是查询)。

答案 7 :(得分:2)

ToList将创建一个新列表并将元素从原始源复制到新创建的列表,所以唯一的办法就是从原始源复制元素并依赖于源大小