如何减少C ++中数据的内存大小?

时间:2013-02-15 16:29:33

标签: c++ memory

我正在研究C ++并使用multimap存储数据。

 struct data
 {
      char* value1;
      char* value2;

      data(char* _value1, char* _value2)
      {
           int len1 = strlen(_value1);
           value1 = new char[len1+1];
           strcpy(value1,_value1);

           int len2 = strlen(_value2);
           value2 = new char[len2+2];
           strcpy(value2,_value2);
      }
      ~data()
      {
           delete[] value1;
           delete[] value2;
      }
 }

 struct ltstr
 {
     bool operator()(const char* s1, const char* s2) const
     {
          return strcmp(s1, s2) < 0;
     }
 };


 multimap <char*, data*, ltstr> m;

示例输入:

  Key               Value
  ABCD123456        Data_Mining Indent Test Fast Might Must Favor List Myself Janki Jyoti Sepal Petal Catel Katlina Katrina Tesing Must Motor blah blah.
  ABCD123456        Datfassaa_Minifasfngf Indesfsant Tfdasest Fast Might Must Favor List My\\fsad\\\self Jfasfsa Katrifasdna Tesinfasfg Must Motor blah blah.
  tretD152456       fasdfa fasfsaDfasdfsafata_Mafsfining Infdsdent Tdfsest Fast Might Must Favor List Myself Janki

输入中有2700万个条目。 输入大小= 14GB

但我注意到内存消耗达到56 GB。我可以知道如何减少内存大小?

6 个答案:

答案 0 :(得分:4)

如果你无法减少实际存储的数据量,你可能想尝试使用一个不同的容器来减少开销(map和multimap有很多)或找到一种方法只保留一部分内存中的数据。

您可能想看一下这些库:

答案 1 :(得分:3)

一种可能性是使用std::map<char *, std::vector<data> >而不是多图。在多图中,您将密钥字符串存储在每个条目中。使用地图,您只有一个密钥字符串副本,并附加了多个data项。

答案 2 :(得分:2)

第一个优化是存储data个对象而不是指针

std::multimap <char*, data, ltstr> m;

因为使用data*会为分配增加额外的内存开销。

另一个是使用pool allocator/Memory pool来减少动态内存分配的占用空间。

如果你有许多相同的密钥字符串,你可以改进它,如果你可以重复使用密钥。

答案 3 :(得分:2)

如果没有看到您的某些数据,有几件事可以改善您项目的内存使用情况。

首先,正如Olaf建议的那样,将数据对象存储在multimap中而不​​是指向它的指针。我不建议为您的数据结构使用池,与直接将其存储在地图中相比,它只会使没有内存节省的事情复杂化。

您可以做的是为您的地图分配std::pair<char*, data>个对象的专用分配器。这可以节省一些开销和堆碎片。

接下来,您应该关注的主要事情是尝试摆脱数据中的两个char*指针。有14个数据,必须有一些重叠。根据它是什么数据,你可以存储它有点不同。

例如,如果数据是名称或关键字,那么将它们存储在中央散列中是有意义的。是的,如上所述,有更复杂的解决方案,如DAWG,但我认为应首先尝试简单的解决方案。

通过简单地将其存储在std::set<std::string>中并将迭代器存储到它,您可以压缩所有重复项,这将节省大量数据。这假设你没有删除字符串。删除字符串将需要您进行一些引用计数,因此您将使用std::map<std::string, unsinged long>之类的内容。我建议你编写一个继承自/包含此哈希的类,而不是将引用计数逻辑放入数据类中。

但是,如果您存储的数据没有很多重叠,例如因为它是二进制数据,所以我建议您将其存储在std::stringstd::vector<char>中。原因是因为现在您可以摆脱数据结构中的逻辑,甚至用std::pair替换它。

我还假设您的密钥不是您存储在数据结构中的指针之一。如果是,请务必将其删除并使用多地图中first的{​​{1}}属性。

根据您存储的数据类型,可能会进一步改进。

因此,有很多假设可能不适用于您的数据,您可能只有这样:

std::pair

答案 4 :(得分:2)

我怀疑你是否泄漏或不必要地重复密钥中的内存。密钥char *字符串来自何处以及如何管理其内存?

如果它们与数据对象中的字符串相同,请考虑使用multiset<data *, ltdata>而不是multimap

如果有许多重复的字符串,请考虑在set<char *,ltstr>中汇集字符串以消除重复字符串。

答案 5 :(得分:2)

我仍然不完全确定这里发生了什么,但似乎内存开销至少是问题的一部分。但是,总体内存消耗约为data结构所需的4倍。如果有27M记录占用14GB,则每条记录大约有500个字节,但占用的空间为56GB。对我来说,这表明存储的数据比我们在这里显示的要多,或者至少有一些数据被存储多次。

“堆存储的额外数据”并没有真正为我做这件事。在linux中,内存分配大约需要32字节的数据。 16字节的开销,分配的内存本身占用16个字节的倍数。

因此,对于存储在multimap中的一条data *条记录,我们需要:

 16 bytes of header for the memory allocation
 8 bytes for pointer of `value1`
 8 bytes for pointer of `value2`
 16 bytes of header for the string in value1
 16 bytes of header for the string in value2
 8 bytes (on average) "size rounding" for string in value 1
 8 bytes (on average) "size rounding" for string in value 2

 ?? bytes from the file. (X)

 80 + X bytes total. 

然后我们在multimap中有char *

 16 bytes of header for the memory allocation. 
 8 bytes of rounding on average. 

 ?? bytes from the file. (Y)

 24 + Y bytes total. 

多图的每个节点都有两个指针(我假设它是某种二叉树):

 16 bytes of header for the memory allocation of the node. 
 8 bytes of pointer to "left"
 8 bytes of pointer to "right"

 32 bytes total. 

因此,在文件中每个条目产生136个字节的“开销”。对于27M记录,这仅仅超过4GB。

正如我所说,该文件每个条目包含500个字节,因此产生14GB。

总共18GB。

所以,在某个地方,某些事情要么是泄漏,要么是数学错误。我可能会在这里进行计算,但即使上面的所有内容都占据了我计算的空间的两倍,但仍有20GB的空间不足。

我们当然可以采取一些措施来节省内存:

1)不要在data中分配两个字符串。首先计算两个长度,分配一个内存块,然后立即将字符串存储起来:

  data(char* _value1, char* _value2)
  {
       int len1 = strlen(_value1);
       int len2 = strlen(_value2);
       value1 = new char[len1 + len2 +2];
       strcpy(value1,_value1);

       value2 = value1 + len1 + 1; 
       strcpy(value2,_value2);
  }

这样每个条目平均可节省24个字节。通过聪明地为数据,value1和value2分配内存,我们可以节省更多。但这可能有点“太聪明”。

2)分配一大块data项目,并一次一个地推出它们也会有所帮助。为此,我们需要一个空构造函数和一个“setvalues”方法:

struct data
{
    ...
    data() {};
    ... 
    set_values(char* _value1, char* _value2)
    {
         int len1 = strlen(_value1);
         int len2 = strlen(_value2);
         value1 = new char[len1 + len2 +2];
         strcpy(value1,_value1);

         value2 = value1 + len1 + 1; 
         strcpy(value2,_value2);
    }
}

std::string v1[100], v2[100], key[100];

for(i = 0; i < 100; i++)
{
    if (!read_line_from_file(key[i], v1[i], v2[i]))
    {
        break;
    }
}    

data* data_block = new data[i]; 

for(j = 0; j < i; j++)
{
    data_block[j].setValues[v1[j].c_str(), v2[j].c_str());
    m.insert(key[i].c_str(), &data_block[j]);
}

同样,这不会节省大量内存,但每个16字节区域可以节省一些内存。以上当然不是完整的代码,更多的是“如何完成它的说明”。

3)我仍然不确定“密钥”在多图中的来源,但如果密钥是value1和value2条目之一,那么你可以重用其中一个,而不是存储另一个副本[假设这就是目前的工作方式]。

我很抱歉,如果这不是一个真正的答案,但我确实认为这是一个答案,在某种意义上说“在某个地方,你在解释你在做什么时有些事情是不明智的”。

了解在您的程序中进行的分配肯定会有所帮助。