使用太多内存的嵌套STL向量

时间:2010-08-21 19:06:40

标签: c++ memory stl vector

我有My_Partition_VectorPartition个对象的STL向量,定义为

struct Partition // the event log data structure
{
    int key;
    std::vector<std::vector<char> > partitions;
    float modularity;
};

Partition.partitions的实际嵌套结构因对象而异,但Partition.partitions中存储的字符总数始终为16。

因此我假设对象的总大小应该大于或等于24字节(16 + 4 + 4)。但是,对于我添加到My_Partition_Vector的每100,000个项目,内存消耗(使用ps -aux找到)增加了大约20 MB,表示每个分区对象大约209个字节。

这增加了近9倍!?所有这些额外的内存使用来自何处? STL向量中的某种填充,还是结构?我该如何解决这个问题(并阻止它进入交换)?

6 个答案:

答案 0 :(得分:3)

有一件事std::vector为动态数组建模,所以如果你知道partitions使用std::vector总是有16个字符,那就太过分了。使用一个好的旧C风格数组/矩阵,boost::arrayboost::multi_array

为减少插入/添加元素所需的重新分配数量,因为它的内存布局约束std::vector允许预先为一定数量的元素预分配内存(并且它是capacity()成员函数会告诉你多少)。

答案 1 :(得分:2)

虽然我认为他可能会夸大局势,但我总体上同意DeadMG的结论,即你所做的就是在寻找麻烦。

虽然我一般都是那个看着(无论有人制造什么样的混乱)并说“不要那样做,只是使用矢量”,但这种情况可能是一个例外。你正在创造大量应该很小的物体。不幸的是,矢量通常看起来像这样:

template <class T>
class vector { 
    T *data;
    size_t allocated;
    size_t valid;
public:
    // ...
};

在典型的32位机器上,已经是12个字节。由于您使用的是vector<vector<char> >,因此外部向量将有12个字节,而对于它所拥有的每个向量,还有12个字节。然后,当您实际在向量中存储任何数据时,每个数据都需要从免费存储中分配一块内存。根据您的免费存储的实现方式,您通常具有最小块大小 - 通常为32或甚至64字节。更糟糕的是,堆通常有一些自己的开销,所以它会在每个块上添加一些更多内存,用于自己的簿记(例如,它可能使用一个链接的列表,添加另一个指向每个分配的指针数据。)

只是为了咧嘴笑,让我们假设你平均每个四个字节的四个向量,并且你的堆管理器有一个32字节的最小块大小和一个额外的指针(或int)用于其簿记(给出一个真正的最小值36字节每块)。相乘,我得到每个204字节 - 足够接近你的209,相信它与你正在处理的相当接近。

那时的问题是如何处理这个问题。一种可能性是尝试在幕后工作。标准库中的所有容器都使用分配器来获取内存。虽然默认分配器直接从免费商店获取内存,但如果您选择,可以替换其他内存。如果你做一些环顾四周,你可以找到任意数量的替代分配器,其中很多/大部分是为了帮助你完全处理你所处的情况 - 减少分配大量小对象时浪费的内存。要看的几个是Boost Pool Allocator和Loki小对象分配器。

另一种可能性(可以与第一种结合使用)将是完全退出vector<vector<char> >,并将其替换为:

char partitions[16];
struct parts { 
    int part0 : 4;
    int part1 : 4;
    int part2 : 4;
    int part3 : 4;
    int part4 : 4;
    int part5 : 4;
    int part6 : 4
    int part7 : 4;
};

目前,我假设最多有8个分区 - 如果它可以是16分,则可以向parts添加更多分区。这可能会减少内存使用量,但是(按原样)会影响您的其他代码。您还可以将其包装到自己的一个小类中,提供2D样式的寻址,以最大限度地减少对代码其余部分的影响。

答案 2 :(得分:1)

如果存储近乎恒定数量的对象,那么我建议使用二维数组。

内存消耗的最可能原因是调试数据。 STL实现通常存储 A LOT 的调试数据。切勿使用调试标志配置应用程序。

答案 3 :(得分:1)

在我的系统上,sizeof(向量)是24.这可能对应于3个8字节成员:容量,大小和指针。此外,您需要考虑内部向量的1到16个字节(加上分配开销)之间的实际分配,以及外部向量(sizeof(vector)* partitions.capacity())的24到384个字节之间的实际分配。 / p>

我写了一个程序来总结一下......

   for ( int Y=1; Y<=16; Y++ )
      {

      const int X = 16/Y;
      if ( X*Y != 16 ) continue; // ignore imperfect geometries

      Partition a;
      a.partitions = vector< vector<char> >( Y, vector<char>(X) );

      int sum = sizeof(a); // main structure
      sum += sizeof(vector<char>) * a.partitions.capacity(); // outer vector
      for ( int i=0; i<(int)a.partitions.size(); i++ )
         sum += sizeof(char) * a.partitions[i].capacity(); // inner vector

      cerr <<"X="<<X<<", Y="<<Y<<", size = "<<sum<<"\n";

      }

结果显示每个简单几何体需要多少内存(不包括分配开销)......

X=16, Y=1, size = 80
X=8, Y=2, size = 104
X=4, Y=4, size = 152
X=2, Y=8, size = 248
X=1, Y=16, size = 440

看看如何计算“总和”以查看所有组件是什么。

发布的结果基于我的64位架构。如果你有一个32位架构,它的大小几乎是你预期的一半 - 但仍然比你预期的要多很多。

总之,std :: vector&lt;&gt;对于进行一大堆非常小的分配来说,空间效率不高。如果要求您的应用程序有效,那么您应该使用不同的容器。


我解决这个问题的方法可能是用

分配16个字符
std::tr1::array<char,16>

并使用将2D坐标映射到数组分配的自定义类来包装它。

下面是一个非常粗略的方法,这是一个让你入门的例子。您必须更改此设置以满足您的特定需求 - 尤其是动态指定几何体的功能。

   template< typename T, int YSIZE, int XSIZE >
   class array_2D
      {
      std::tr1::array<char,YSIZE*XSIZE> data;
   public:
      T & operator () ( int y, int x ) { return data[y*XSIZE+x]; } // preferred accessor (avoid pointers)
      T * operator [] ( int index ) { return &data[index*XSIZE]; } // alternative accessor (mimics boost::multi_array syntax)
      };

答案 4 :(得分:1)

...这是一个侧面对话,但建议使用boost :: multi_array作为OP使用嵌套向量的替代方法。我的发现是,当应用于OP的操作参数时,multi_array使用了相似数量的内存。

我从Boost.MultiArray的示例中获取了此代码。在我的机器上,这表明multi_array使用的内存比理想情况下多10倍,假设16个字节以简单的矩形几何排列。

为了评估内存使用情况,我在程序运行时检查了系统监视器,并使用

编译
( export CXXFLAGS="-Wall -DNDEBUG -O3" ; make main && ./main )

这是代码......

   #include <iostream>
   #include <vector>
   #include "boost/multi_array.hpp"
   #include <tr1/array>
   #include <cassert>

   #define USE_CUSTOM_ARRAY 0 // compare memory usage of my custom array vs. boost::multi_array

   using std::cerr;
   using std::vector;

  #ifdef USE_CUSTOM_ARRAY
   template< typename T, int YSIZE, int XSIZE >
   class array_2D
      {
      std::tr1::array<char,YSIZE*XSIZE> data;
   public:
      T & operator () ( int y, int x ) { return data[y*XSIZE+x]; } // preferred accessor (avoid pointers)
      T * operator [] ( int index ) { return &data[index*XSIZE]; } // alternative accessor (mimics boost::multi_array syntax)
      };
  #endif

int main ()
   {

   int COUNT = 1024*1024;

  #if USE_CUSTOM_ARRAY
   vector< array_2D<char,4,4> > A( COUNT );
   typedef int index;
  #else
   typedef boost::multi_array<char,2> array_type;
   typedef array_type::index index;
   vector<array_type> A( COUNT, array_type(boost::extents[4][4]) );
  #endif

  // Assign values to the elements
  int values = 0;
  for ( int n=0; n<COUNT; n++ )
     for(index i = 0; i != 4; ++i) 
       for(index j = 0; j != 4; ++j)
           A[n][i][j] = values++;

// Verify values
   int verify = 0;
    for ( int n=0; n<COUNT; n++ )
       for(index i = 0; i != 4; ++i) 
          for(index j = 0; j != 4; ++j)
             {
             assert( A[n][i][j] == (char)((verify++)&0xFF) );
            #if USE_CUSTOM_ARRAY
             assert( A[n][i][j] == A[n](i,j) ); // testing accessors
            #endif
             }

   cerr <<"spinning...\n";
   while ( 1 ) {} // wait here (so you can check memory usage in the system monitor)

   return 0;
   }

答案 5 :(得分:0)

16个字节是完全浪费的。你存储了很多关于非常小的物体的数据。向量向量是使用的错误解决方案。你应该记录sizeof(向量) - 它并不是无关紧要的,因为它执行一个重要的功能。在我的编译器上,sizeof(向量)是20.因此每个分区是4 + 4 + 16 + 20 + 20 *内部分区的数量+内存开销,例如矢量不是完美的大小。

你只存储了16个字节的数据,浪费了大量内存,以你可能想到的最分离,最高开销的方式分配它们。矢量不会占用大量内存 - 你的设计很糟糕。