时间:2010-07-30 13:56:14

标签: c++ visual-c++ memory-management

我在Visual C ++ 9程序中遇到以下问题。有一个巨大的对象,逻辑上包含几个子对象。我可以将子对象存储在对象中,也可以存储指向单独分配的子对象的指针。

这里的关键点是,在一个外部对象中始终存在每个类型的子对象的一个​​实例 - 它总是具有相同的大小,并且其生命周期与外部对象的生命周期完全相同,因此上面的两个选择在逻辑上相同 - 程序结构和逻辑不会改变。

以下是我的思想发展方式:

  1. 如果我将子对象存储在遍历它们内部会变得稍微快一点,但这不是我最关心的 - 我描述了程序,这不是瓶颈。
  2. 如果我在外部对象中存储子对象将占用更多内存,那么我将分配更大的块,并且与分别分配大量类似的小子对象相比,可能会分割内存更多 - 释放的内存块可能不太可重复使用(我是不确定,我只是想那个。)。
  3. 当然,较少的分配会使程序更快,但这不是很重要 - 我再次分析了程序,分配时间不是瓶颈。
  4. 由于每次分配会导致较大对象的内存开销,实际上我会消耗更少的内存,这对我处理大量数据的程序有利。
  5. 与此同时,内存碎片让我感到困扰 - 我需要我的程序非常稳定,能够连续几个月运行。

    我如何做出决定?考虑到上述考虑因素,是否有任何方法可以决定是否应该选择较小的物体或较大的物体?

6 个答案:

答案 0 :(得分:2)

分配一些大对象会使内存碎片少于分配大量较小的对象。更重要的是,由于较大比例的对象大小完全相同,因此您通常可以完全按原样重用已释放的块。将对象拆分为大量较小的对象通常会导致问题的碎片更多,而不是更少。

如果您发现碎片 成为问题,您通常希望通过定义自己的分配器来处理它。

答案 1 :(得分:1)

我想说通过存储一个大对象而不是许多小对象,你将获得更少的内存碎片。这样想吧。如果你有100 MB的内存,你分配1个50MB的对象,那么在最坏的情况下,你将有两个25MB的可用块。

但是,如果你分配了两个25 MB块,那么最坏的情况下你可能有3个16MB块。这是更多的碎片,而不是更少。

答案 2 :(得分:1)

我不建议根据在分散方面的优势来做出这个决定。而是根据您的班级设计做出决定。

如果子对象不在包含对象的公共接口中播放任何部分,则可以对它们进行pimpl,从而减少外部对象的公共可见界面以及可能的编译时间。然后,您可以私下定义隐藏在实现中的子对象的实现,而无需公开可见性。

或者,如果您的设计受益于自动管理的便利,如果直接包含的对象使用该方法。

根据设计考虑做出此决定后,如果您仍然担心碎片问题,解决该问题的正确方法是使用自定义分配器自己控制分配,而不是依赖内置分配器的任何特定行为由new使用。

答案 3 :(得分:0)

执行尽可能少的单独分配是一个非常优越的选择。这是因为分配涉及在内存和时间上减少的开销,此外,连续内存的访问速度要快得多,因为缓存丢失的风险大大降低,并且还减少了碎片(尽管我从来没有过个人碎片的问题)。

简单的事实是,在每个可能的方面,与子对象组合是最快和最小的选择。如果你能做到,那就去做吧。

它还有许多与维护相关的好处,例如自动构造函数/赋值运算符代。

答案 4 :(得分:0)

如果您使用的是智能指针,则不必担心会破坏您的对象,因此代码大致相当于使用聚合对象(除了内存和性能上的轻微开销)。 但是,随着经验规范随时间的变化,(您可能需要稍后在对象之间交换子对象),使用指针比使用子对象更灵活。在更糟糕的情况下(如果你有内存或性能)并想要返回子对象,你可以轻松创建一个子对象并使旧指针指向它,所以你不要改变除构造函数之外的任何代码位,像那样(伪代码) (你甚至可以使用#ifdef,你可以随时根据需要做一些基准测试来改变它)

class A 
{
#ifdef SUBOBJECT
   B __b; 
#endif
   B *b;
   A()
   {
#ifdef SUBOBJECT
     *b = &__b;
#else
     *b = new B();
#endif     
   };

  void f()
  {
    b->f();
  }
}

答案 5 :(得分:0)

正如其他人在这里提到的那样,你假设许多小分配比碎片分配更好而不是更少的大分配是不正确的。

如果碎片是一个大问题,那么您可以使用的最好的工具之一是池内存。你在问题中说“子对象总是大小相同”。如果这是真的,那么您将有很大的机会大大减少分配数量和堆碎片。只需创建自己的分配器对象即可分发sizeof(SubObject)的内存块。您的外部对象将存储指向子对象的指针,该子对象已与分配器一起分配。当它想要解除分配时,它将内存交给分配器对象。分配器对象(而不是调用delete)将保存内存块,并在下次请求分配时将其移出。 allocator对象将存储这些块的列表并回收它们。这将减少您极大地完成的堆分配数。