哪个更快:清除集合或实例化新的

Posted

技术标签:

【中文标题】哪个更快:清除集合或实例化新的【英文标题】:Which is faster: clear collection or instantiate new 【发布时间】:2012-06-09 16:33:30 【问题描述】:

我的代码中有一些通用列表,其中包含数十或数百个元素。 有时我需要用其他对象重新填充这个列表,所以问题是:调用Clear() 方法或创建new List<T>() 会更快吗?

【问题讨论】:

@Jason:IMO *** 专家并不完全是“互联网上的陌生人”。如果有人已经研究过这个问题,他将能够轻松回答。但是你确实有一个很好的观点,如果你想确定,你应该自己计算。 Jason,我相信,对于任何情况,我都应该知道一些共同的理论,而不仅仅是我的情况。 @Mrimsh:我看不出这会如何改变我的答案:自己测量。 @Mrimsh 问题在于“通用理论”取决于很多因素,其中许多取决于您的确切使用场景。在不了解很多其他信息的情况下,无法说一个版本比另一个版本更好,因为它们具有不同的性能特征 - 在特定场景下,每个版本都可以更好/更快/使用更少的内存等。 +1,因为这个问题要求的答案是让人们在测试之前就可以对预期结果做出明智的猜测。 【参考方案1】:

什么会更快,调用Clear() 方法或创建一个`new List()?

这是无法回答的。这实际上取决于很多因素,包括收藏品存在多长时间。

这里最好的选择是:

    分析应用程序,看看这是否真的很重要。它可能不会产生任何明显的差异,在这种情况下,我会使用最符合您对这个对象的看法的方法。

    如果确实重要,请编写两组代码,并测量速度差异(如果有)。

从实际的角度来看,调用Clear() 实际上不会减少内存(由List<T> 本身使用),因为它不会缩小列表的容量,只会消除其中包含的值。创建一个新的List<T> 将导致分配一个新列表,这反过来又会导致更多的分配增长。

然而,这并不意味着它会变慢 - 在许多情况下,重新分配会更快,因为您不太可能将大型数组提升为更高的垃圾回收代,这在turn 可以使 GC 过程保持得更快。

如果不知道您的确切场景并在探查器中测量,则无法知道您的场景中哪个更好。

【讨论】:

+1 到“测量”。正如@NominSim 所指出的,清除操作可能成本高昂(O(n),因为要清除每个元素,尤其是对于大型列表),因此针对特定场景进行分析是唯一有效的选项。 @Alexei Levenkov:关于 Array.Clear() 的 O(n) 复杂性 - 由于更好的局部性,使用简单的值类型,该因素可能很小。并且,是的 + 1 来衡量 +1 用于解释 Clear()new List<T> 在 GC 和内存分配方面的区别。 我的函数式程序员更喜欢重新初始化变量而不是改变引用,而我的系统程序员更喜欢重用引用(不一定意味着更高效) .【参考方案2】:

我已经运行了这个测试:

private static void Main(string[] args)

    int defaultN = 1000;

    Stopwatch sw = new Stopwatch();

    while (true)
    
        Console.WriteLine("Enter test elements number:");
        int n;
        if (!int.TryParse(Console.ReadLine(), out n)) n = defaultN;
        else defaultN = n;

        Console.WriteLine($"Test with n elements");

        List<object> list = Enumerable.Repeat(new object(), n).ToList();
        sw.Start();
        Clear(list);
        sw.Stop();
        Console.WriteLine("Clear: 0 ms", sw.ElapsedTicks / 10000D);

        GC.Collect();
        GC.WaitForPendingFinalizers();

        List<object> list2 = Enumerable.Repeat(new object(), n).ToList();
        sw.Restart();
        Reinitialize(list2);
        sw.Stop();
        Console.WriteLine("Reinitialize: 0 ms", sw.ElapsedTicks / 10000D);

        GC.Collect();
        GC.WaitForPendingFinalizers();

        List<object> list3 = Enumerable.Repeat(new object(), n).ToList();
        sw.Restart();
        ReinitializeAndCollect(list3);
        sw.Stop();
        Console.WriteLine("ReinitializeAndCollect: 0 ms", sw.ElapsedTicks / 10000D);

        Console.WriteLine("===");
    

private static List<object> Clear(List<object> list)

    list.Clear();
    return list;

private static List<object> Reinitialize(List<object> list) => new List<object>();
private static List<object> ReinitializeAndCollect(List<object> list)

    list = new List<object>();

    GC.Collect();
    GC.WaitForPendingFinalizers();

    return list;

我的结论基于我的普通核心 i3 处理器的结果:

如果有数千个元素 - 最好清除列表。它速度快,内存效率高。

如果集合有超过 100 000 个元素 - 重新初始化变得更有吸引力。如果在分析之后您认为这里存在瓶颈,请使用它。重新初始化会非常快,但正如第三种方法测试所示,未来的垃圾收集将与清除列表一样慢。

简短的回答是:如果您没有分析您的应用程序,请使用Clear。重用对象是好的。如果你这样做了——你已经知道该怎么做了。

【讨论】:

【参考方案3】:

这将取决于很多因素,从长远来看,它在您的程序中可能并不重要(足以计算在内)。

来自 msdn docs .Clear() 是 O(n) 操作。

初始化一个新实例将有它自己的开销以及(如果你保持集合相同的长度,一个 O(n) 操作:即 n 个 Add() 调用)。

真正测试这一点的唯一方法是在你的程序中设置一些秒表,看看如果你真的认为它是值得的,会产生什么效果。在所有的可能性;这不值得。

我的想法是,如果你已经创建了一个集合,Clear() 它,这就是为什么首先有一个 Clear() 方法。

【讨论】:

+1。我会说这是非常重要的区别,因为与 Add 不同,Clear 必须清空每个元素。【参考方案4】:

虽然这可能令人沮丧,但答案确实是无关紧要。两者之间的时间差异将非常小,以至于它可能不会对您的应用程序产生任何影响。尽一切可能使代码更清晰、更易于理解,并尽量不要为微优化而编程。

【讨论】:

+1 对,否则这个问题有点过早优化的味道。 这实际上可能非常重要 - 但您很可能是正确的。仅当测量显示存在问题时才应考虑这一点 - 但我认为假设它不/不应该重要是不公平的...... @ReedCopsey 当然 - 在 极不可能 事件中,分析器在清除(或分配新)集合时显示问题,那么值得研究。我的观点是,基于多年的软件开发经验,这两个操作之间的时间差成为影响应用程序性能的主要因素的可能性基本上为零,而在预期另一个对代码产生负面影响的情况下使用一个的可能性的正确性更加明显。 @ChrisShain 是的 - 我同意,但实际上我已经在我的代码中将此作为一个可衡量的、重要的热点。如果在某种形式的循环中使用 Clear() 并导致您的集合升级为 Gen1 或 Gen2,则使用 Clear() 确实有可能出现实际问题。虽然我完全同意这在大多数应用程序中不太可能成为问题,但我仍然认为在不知道原始海报没有测量的情况下假设它无关紧要是不公平的。【参考方案5】:

Clear() 将删除所有元素,并保持现有容量,而创建新 List 将需要从托管堆中分配至少一个(如果初始容量较小,可能会添加更多项)。

如果您有大量项目,并且每次迭代的项目数量大致相同,那么使用Clear 可能会稍微快一些。

如果您在一次迭代中拥有非常多的项目,那么在随后的迭代中数量会少得多,那么使用 Clear 的成本可能更高,因为您将在内存中保存一个带有不必要的大容量。

当然,在许多(大多数?)场景中,差异可以忽略不计。

【讨论】:

【参考方案6】:

也许我在这里做了一些根本错误的事情,但是在使用 C# 开发 ASP.NET 应用程序时,我在使用 Clear() 与 new 时遇到了很大的不同。 我正在创建一个带有图表的统计页面,其中包含数据系列。 对于每个图表,我都有一个部分执行此操作:

chart = new ChartistChart()  Title = "My fancy chart" ;
series = new List<ChartistMetaValue>();
*some code for getting the statistics*
chart.Series.Add(series);
chartistLineCharts.Add(chart);

然后是另一个图表。

chart = new ChartistChart()  Title = "My second fancy chart" ;
series = new List<ChartistMetaValue>();
*some code for getting the statistics*
chart.Series.Add(series);
chartistLineCharts.Add(chart);

使用new 重新分配series 时效果很好,但是当我这样做时

series.Clear();

相反,我实际上清除了chart.SerieschartistLineCharts 中的条目,因此统计页面最终只检索到最后一个图表的系列。我假设这里有一些链接,比如内存指针,这与最初讨论的问题不同,但这至少是选择new 而不是Clear() 的原因。不过,也许有办法避免它。

【讨论】:

好点。 .Clear 正在改变原始引用,它可能会在您当前感兴趣的变量之外产生影响。 嗯,两个图表需要两个系列。您可以使用相同的变量独立处理两者,但不能清除相同的变量。【参考方案7】:

我为自己做了几个测试。结果(速度)是:

对于小列表 - 例如 3 个项目,创建新列表的速度更快,但差别不大 对于平均 10 个或更多项目,最好清除列表。值类型要好得多(例如 3-4 倍),值时间要好 20%。

但最终,最好对应用程序进行剖析并找到整个应用程序的瓶颈。

【讨论】:

【参考方案8】:

如果您的对象是值类型,我会使用 Clear() 来减少内存未来分配。否则这两种方法几乎相同。

【讨论】:

-1。这不是真的 - Clear 不会减少后备数组的大小,因此根本不会减少值类型的内存使用量。或者你的意思可能是别的…… @Alexei Levenkov 它将减少未来的内存分配。 OP 的意图是立即重新填充它。

以上是关于哪个更快:清除集合或实例化新的的主要内容,如果未能解决你的问题,请参考以下文章

为每个 TableViewCell 实例化新数组

在 ASP.NET Web API 控制器的 nunit 测试中实例化新的 System.Web.Http.OData.Query.ODataQueryOptions

Unity 在销毁前一个对象 1 秒后实例化新对象

实例化新 VC 后销毁当前 tabbarVC

使用猫鼬崩溃在 NestJS 中实例化新文档

依赖注入概念问题:在哪里实例化新对象以保存到数据库?