【问题标题】:Keeping C#'s List<> performance efficient保持 C# 的 List<> 性能高效
【发布时间】:2014-05-08 01:54:55
【问题描述】:

我的应用程序将包含列表。一个列表中的每个对象都必须绑定到另一个列表中的对象。我的想法是通过列表中的索引来访问它们。

List<MyType1> listNrOne;
List<MyType2> listNrTwo;

foreach (int index in indexes)
{
    someObj.SomeFunc(listNrOne[index], listNrTwo[index]);
}

另外,listNrOne[index] 和 listNrTwo[index] 将与现实生活中的对象相关联。

  • 有时根本不需要列表中的对象 - 为使列表保持特定顺序,索引项将为空。
  • 有时需要并创建一个对象,例如在循环中使用 10 秒并不断更新,然后就不再需要了。

我想保持应用程序内存高效。所以我不会让 GC 收集不必要的对象并重用它们。所以:

// The application does not need listNrTen[11]    
queueOfNotNeeded.Enqueue(listNrTen[11]);
listNrTen[11] = null;
// ...
// suddenly an object of the type is needed
// so instead of creating a new one
// the application will reuse existing ones (one that wasn't GCd)
// listNrTen[159] is currently null
listNrTen[159] = queue.Dequeue();

问题: 这会导致内存碎片化吗?随着时间的推移,它会保持多高的效率?

编辑:应用程序是一个游戏,所以性能很重要。

【问题讨论】:

  • 该模式称为对象池(或享元,具体取决于具体情况)——您应该研究一下。
  • 在模糊的上下文中谈论效率也很困难。您会受益于设置一种在 您的 上下文中测试不同选项的方法
  • 很高兴知道,但我到处都看到使用了 Dictionary 和 HashMap。所以我看不出这有什么帮助。
  • 你能解释一下你所期望的记忆碎片吗?
  • 对象以块的形式创建(例如:同时创建 15 个)。循环需要在循环中有效地访问它们。如果应用程序删除了第 15 个引用。然后将之前的第 15 个对象设为第 4 个。它需要跳过内存(不确定)。

标签: c# list generics


【解决方案1】:

不确定我是否遵循,但按索引配对列表对象通常不是好的设计。您将对象存储为类的成员,并保留该类类型的单个列表。 .Net 已经提供了一个类,您可以使用该类对对象进行分组,而无需定义一个仅用于您的列表的新类:Tuple

var Nr = new List<Tuple<MyType1, MyType2>>();

当你有空位时,你可以创建一个元组,其中一个元素是null

Nr.Add(Tuple<MyType1, MyType2>.Create(null, MyType2Instance));

但是,元组只为您提供大小不超过 8 的元组。我看到您需要至少 10 个的指示。所以我们可能会回到您想要定义自定义类的点,其中包含要保存的字段您正在使用的每种类型的实例。


我也可以说,您尝试使用 nulls 执行的操作、缓存空引用以节省内存以及创建块中的对象以节省时间都是行不通的。您仍然需要为每个对象调用一次new 来分配空间并创建对象,并且.Net 已经在后台为您批量分配内存。尤其是以 15 个块创建对象并不是处理这个问题的好方法。

我的建议是将性能视为一个工程问题,在这种情况下,您需要依靠实际测量来告诉您代码慢的地方,并以这种方式分配您的编程时间。换句话说,一开始就去构建这个东西而不必太担心它。然后使用分析器告诉您需要调整哪些代码才能为您带来最大收益。它几乎总是在你想不到的地方。


“循环遍历相同类型的对象很重要。”

好的。使用类型/类建议,循环遍历上述 Nr 列表中 MyType1 位置的所有对象:

foreach( var item in Nr.Where(i => i.Item1 != null).Select(i => i.Item1))
{
    //...
}

但这听起来越来越像你在朝着完全错误的方向前进,通过尝试构建一个你认为对大量对象更有效的数据结构,而不是尝试构建一个数据结构,很好地映射到您的问题空间并有效地仅跟踪您需要的对象之间的关系。

【讨论】:

  • 我正在尝试实现一个实体系统。在这个系统中,循环遍历相同类型的对象很重要。这些对象应该保存简单的数据。所以我需要一堆类型。
【解决方案2】:

只要不fix内存,它就不会碎片化。如果你不使用GCHandles 和不安全的代码,你可能是安全的。在 .NET 中,托管堆偶尔会被压缩一次,这会消除内存碎片,只要您不阻止它(例如,通过长时间固定句柄)。

但是,即使是在游戏中,我也会在您真正确定自己遇到真正的问题之前小心进行此类优化。也就是说,重用 List 是一种简单的优化;但是,请确保您不要一次分配 500 MiB 列表,并且在常用情况下不要超过 10 MiB - 如果确实发生这种情况,您将需要实施一些“压缩”,即。如果 List 变得太空,您可能需要重新创建它而没有空格。

重要的是,这只有在您确实遇到与 List 分配相关的性能问题时才有意义。我怀疑情况是否如此。在实践中,我希望初始化对象本身会比您从这些微优化中获得的任何东西要慢得多,尤其是如果您大部分时间都没有保持列表几乎满。

分析性能。你会看到你有多在乎。托管编程的伟大之处在于,以后在这些微优化中改变主意通常非常容易。如果你真的想要,你可以把它隐藏在一个非常薄的抽象后面(这个模式看起来很像一个工厂 - 立即“天真地”实现它,它不会消耗太多性能(它可能会被完全优化掉),如果遇到问题,只需更改工厂即可)。

【讨论】:

    【解决方案3】:

    我建议您稍后再担心实现的性能,并将所有这些细节隐藏在接口下。坚持目前有效的简单方法,并在发现未达到性能目标时进行优化。您还可以知道您应该优化什么,因为您认为会成为应用程序瓶颈的事情往往是无关紧要的。

    此外,您应该知道存在一种简单的机制可以让您“复活”对象。这种机制的主要(也是唯一的用例,据我所知)是对象池场景。通常不建议这样做,但如果重建容器确实是一个瓶颈,那么这是一个比以您描述的方式存储它更简洁的解决方案。

    以下是相关链接:

    Usages of object resurrection

    http://blogs.msdn.com/b/abhinaba/archive/2009/04/13/object-resurrection-using-gc-reregisterforfinalize.aspx

    【讨论】:

      【解决方案4】:

      值得注意的是,CLR 的内存分配针对频繁的对象分配进行了优化,因为它通常从堆顶分配(非常快),然后在垃圾收集期间依靠定期堆压缩来保持空间可用的。这与许多 C++ 编译器使用的传统内存分配模型不同,例如,有时必须搜索堆以找到新对象的空白空间。因此,通过重用对象,您可能没有想象的那么节省。事实上,您实际上可能通过减少内存局部性和影响内存缓存来降低性能(重用的实例不会在堆内存中靠近您最近分配的其他东西,您经常希望一起使用它们。)

      这个故事的寓意是,您通常希望首先从一种简单且易于维护的方法开始,然后在构建完成后对其进行分析以识别您真正的性能瓶颈。对于现代 PC,很容易“理论上”错误地识别出真正的瓶颈,并通过尝试优化而最终对性能的改善甚微甚至损害。

      【讨论】:

        【解决方案5】:

        我建议您最好使用 exposed-field 结构的 array(而不是 List)。创建数组将为每个元素分配一个结构实例空间,并且该空间将在数组的生命周期内保持分配。读取或写入存储在数组中的结构的 exposed 字段或另一个结构的 exposed 字段所需的时间,将结构作为 ref 参数传递,或读取或写入作为ref 参数接收的结构的暴露 字段,都不受结构大小的影响。如果您只在您真正想要复制其中包含的所有信息的情况下复制结构,您可以忽略任何建议您避免使用超过 16 个字节的结构的建议。

        .NET 对结构设计的建议假定人们正在设计将像对象一样使用的结构。如果要设计像结构一样使用的结构,则规则完全不同:“结构式”结构的状态应该是其public字段内容的总和,以及每个字段的含义应该只是“有人写到这个领域的最后一件事”。仅在需要与其他代码交互时使用实例方法或属性(例如,通过Equals(Object)IEquatable&lt;T&gt;.Equals(T)GetHashCode()ToString());否则使用static 方法方法,这些方法将结构作为ref 参数。

        结构数组在 .NET 中提供了非常好的性能;使用数组和Count,你可以做任何可以用List&lt;T&gt; 做的事情;必须手动调整数组的大小可能有点麻烦,但是能够对存储在数组中的结构元素进行操作是一个主要的性能优势。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-16
          • 2016-07-23
          • 2015-07-16
          相关资源
          最近更新 更多