保持c# '列表<>高效的性能

本文关键字:高效 性能 列表 保持 | 更新日期: 2023-09-27 17:54:25

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

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();

问题:这会分割内存吗?随着时间的推移,它的效率如何?

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

保持c# '列表<>高效的性能

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

这个故事的寓意是,您通常希望首先使用简单且易于维护的方法,然后在构建完成后进行分析,以确定真正的性能瓶颈。在现代pc中,很容易错误地识别出"理论上"的真正瓶颈,并通过尝试优化来最小化改进甚至损害性能。

我建议您稍后再考虑实现的性能,并将所有这些细节隐藏在接口下面。坚持做一些简单的事情,当你发现你没有达到你的性能目标时进行优化。您还可以告诉您应该优化什么,因为通常您认为会使应用程序成为瓶颈的事情最终都是无关紧要的。

另外,你应该意识到存在一个简单的机制,允许你"恢复"对象。这种机制的主要(也是唯一的用例,据我所知)是对象池场景。通常不建议这样做,但如果重新构建容器确实成为瓶颈,那么这是一个比以您描述的方式存储容器更简洁的解决方案。

以下是相关链接:

对象复活的用法

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

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

然而,即使是在游戏中,我也会在确定存在真正的问题之前谨慎对待这样的优化。也就是说,重用List是一种简单的优化;但是,请确保不要一次分配500 MiB的列表,并且在通常情况下不要超过10 MiB -如果确实发生了这种情况,您将需要实现一些"压缩",即。如果List太空,您可能想要重新创建它,不使用空格。

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

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

我不确定我是否遵循,但按索引配对列出对象通常不是好的设计。. net已经提供了一个类,你可以用它来对对象进行分组,而不需要为你的list定义一个新的类:Tuple。

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

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

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

然而,tuple只提供给你大小不超过8的。我看你至少需要10个。所以我们很可能会回到你想要定义一个自定义类的地方,用一个字段来保存你正在使用的每个类型的实例。


我也可以说,你试图做什么null s,缓存空引用来节省内存,并在块中创建对象来节省时间只是不会工作。您仍然需要为每个对象调用一次new来分配空间并创建对象,并且. net已经在幕后为您批量处理内存分配。创建15块的对象并不是处理这个问题的好方法。

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


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

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

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

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

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

. net关于设计结构的建议假设我们正在设计的结构将像对象一样使用。如果一个人正在设计像结构一样使用的结构,那么规则就完全不同了:一个"结构风格"的结构的状态应该是其public字段内容的总和,每个字段的含义应该简单地是"有人写到这个字段的最后一件事"。仅在需要与其他代码接口时使用实例方法或属性(例如通过Equals(Object), IEquatable<T>.Equals(T), GetHashCode()ToString());否则使用static方法,这些方法将结构体作为ref参数。

结构数组在。net中提供了非常好的性能;使用数组和Count,你可以做任何可以用List<T>完成的事情;手动调整数组的大小可能有点麻烦,但是操作存储在数组中的结构元素的能力是一个主要的性能优势。