调用ToList()时是否会影响性能?
当使用ToList()
,是否有需要考虑的性能影响?
我正在写一个查询来从目录中检索文件,这是查询:
string[] imageArray = Directory.GetFiles(directory);
但是,因为我喜欢与List<>
工作,所以我决定放入…
List<string> imageList = Directory.GetFiles(directory).ToList();
那么,在决定做这样一个转换时,是否应该考虑到某种性能影响 – 或者只是在处理大量文件时才考虑这种影响? 这是一个微不足道的转换?
IEnumerable.ToList()
是的, IEnumerable<T>.ToList()
确实会对性能产生影响,这是一个O(n)操作,尽pipe在性能关键操作中可能只需要注意。
ToList()
操作将使用List(IEnumerable<T> collection)
构造函数。 这个构造函数必须复制一个数组(更一般的IEnumerable<T>
),否则对原始数组的未来修改也会在源T[]
上发生变化,这通常是不可取的。
我想重申一下,这只会与一个巨大的列表有所不同,复制大块内存是相当快的一个操作。
方便的提示, As
vs
你会注意到在LINQ中有几个以As
开头的方法(如AsEnumerable()
)和To
(如ToList()
)。 以To
开始的方法需要像上面那样的转换(即可能会影响性能),以As
开头的方法不需要,只需要一些转换或简单的操作。
有关List<T>
其他详细信息
这里有一些关于List<T>
如何工作的细节,以防你感兴趣:)
一个List<T>
也使用一个叫做dynamic数组的构造,它需要根据需要resize,这个resize事件将旧数组的内容复制到新数组中。 所以如果需要的话,它会从小尺寸开始增加尺寸 。
这是List<T>
上的Capacity
和Count
属性之间的区别。 Capacity
是指幕后数组的大小, Count
是List<T>
中总是<= Capacity
。 所以当一个项目被添加到列表中,增加它超过Capacity
, List<T>
的大小加倍,数组被复制。
调用toList()时是否会影响性能?
当然是。 理论上,即使i++
对性能有影响,它也会使程序的运行速度减慢几个小时。
.ToList
做什么的?
当您调用.ToList
,代码会调用Enumerable.ToList()
,这是一个return new List<TSource>(source)
的扩展方法。 在相应的构造函数中, 在最坏的情况下,它通过物品容器并将它们逐个添加到新的容器中。 所以它的行为对性能影响不大。 要成为应用程序的性能瓶颈是不可能的。
问题中的代码有什么问题
Directory.GetFiles
遍历该文件夹,并立即将所有文件的名称返回到内存中,这有潜在的风险,即string[]耗费大量内存,使所有内容变慢。
那么应该做些什么呢?
这取决于。 如果你(以及你的业务逻辑)保证文件夹中的文件总是很小,那么代码是可以接受的。 但仍build议在C#4中使用懒惰版本: Directory.EnumerateFiles
。 这更像是一个查询,不会立即执行,你可以添加更多的查询,如:
Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))
一旦find名为“myfile”的文件,将停止searchpath。 这显然有更好的performance.GetFiles
。
调用toList()时是否会影响性能?
就在这里。 使用扩展方法Enumerable.ToList()
将从IEnumerable<T>
源集合构造一个新的List<T>
对象,这当然会对性能产生影响。
但是,理解List<T>
可能会帮助您确定性能影响是否显着。
List<T>
使用数组( T[]
)来存储列表的元素。 数组一旦被分配就不能被扩展,所以List<T>
将使用一个超大的数组来存储列表的元素。 当List<T>
超出大小的基础数组时,必须分配一个新数组,并且必须将旧数组的内容复制到新的较大数组中,然后列表才能增长。
当从IEnumerable<T>
构造一个新的List<T>
IEnumerable<T>
,有两种情况:
-
源集合实现
ICollection<T>
:然后ICollection<T>.Count
用于获取源集合的确切大小,并在源集合的所有元素被复制到支持数组之前使用ICollection<T>.CopyTo()
。 这个操作非常高效,可能会映射到一些CPU指令来复制内存块。 但是,就性能而言,新arrays需要内存,而复制所有元素需要CPU周期。 -
否则,源集合的大小是未知的,并且
IEnumerable<T>
的枚举IEnumerable<T>
用于将每个源元素一次添加到新的List<T>
。 最初支持数组是空的,并创build一个大小为4的数组。 那么当这个数组太小时,size就会增加一倍,所以backing数组就像这个4,8,16,32等一样增长。每当backing数组增长的时候,它都必须被重新分配,并且所有存储的元素都必须被复制。 与第一种情况相比,这种操作成本更高,可以立即创build正确大小的数组。此外,如果你的源代码集合包含33个元素,那么这个列表将最终使用一个64个元素的数组,浪费一些内存。
在你的情况下,源集合是一个实现了ICollection<T>
的数组,所以性能影响不是你应该关心的,除非你的源数组非常大。 调用ToList()
将简单地复制源数组并将其包装在List<T>
对象中。 即使是第二种情况的performance也不足为小的collections担心。
“是否有需要考虑的性能影响?”
精确场景的问题在于,首先,您对性能的真正关注来自硬盘caching的硬盘驱动器速度和效率。
从这个angular度来看,这个影响肯定是微不足道的,它不需要考虑。
但是只有当你确实需要List<>
结构的特性,才有可能让你更有效率,或者你的algorithm更友好,或者其他一些优势。 否则,你只是故意添加一个微不足道的performance,毫无理由。 在这种情况下,你自然不应该这样做! 🙂
ToList将创build一个新的列表,并将元素从原始来源复制到新创build的列表中,所以唯一的办法是从原始来源复制元素,并依赖于来源大小
ToList()
创build一个新的List,并将其中的元素,这意味着有做ToList()
相关的成本。 在收集小的情况下,它不会是非常明显的成本,但有一个巨大的收集可以导致使用ToList的情况下的性能打击。
一般来说,你不应该使用ToList(),除非你所做的工作不能在没有将集合转换为List的情况下完成。 例如,如果您只想遍历集合,则不需要执行ToList
如果使用LINQ to SQL对数据源(例如数据库)执行查询,那么执行ToList的代价更大,因为当您将ToList和LINQ to SQL一起使用而不是执行延迟执行时,即在需要时加载项目(这可能是有益的在许多情况下)它立即从数据库加载项目到内存中
它将像在做一样有效率:
var list = new List<T>(items);
如果反汇编带有IEnumerable<T>
的构造函数的源代码,你会发现它会做一些事情:
-
调用
collection.Count
,所以如果collection
是一个IEnumerable<T>
,它将强制执行。 如果collection
是一个数组,列表等,它应该是O(1)
。 -
如果
collection
实现ICollection<T>
,它将使用ICollection<T>.CopyTo
方法将项目保存在内部数组中。 它应该是O(n)
,是n
的长度。 -
如果
collection
没有实现ICollection<T>
,它将遍历集合的项目,并将它们添加到内部列表中。
所以,是的,它将消耗更多的内存,因为它必须创build一个新的列表, 在最坏的情况下,它将是O(n)
,因为它将遍历collection
来复制每个元素。
考虑到检索文件列表的性能, ToList()
是微不足道的。 但其他情况并不是真的。 这真的取决于你在哪里使用它。
-
在调用数组,列表或其他集合时,将以
List<T>
创build集合的副本。 这里的performance取决于列表的大小。 你真的有必要的时候应该这样做。在你的例子中,你可以在数组上调用它。 它遍历数组并逐个将项添加到新创build的列表中。 所以性能影响取决于文件的数量。
-
当调用一个
IEnumerable<T>
,你实现IEnumerable<T>
(通常是一个查询)。