2013-02-15 46 views
2

我正在使用C++并使用multimap来存储数据。如何减少C++中数据的内存大小?

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 

有在输入端27万个条目。 输入大小= 14GB

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

+2

您是否尝试过轻量级设计模式? – Deamonpog 2013-02-15 16:33:35

+1

您可以查看[boost平面关联容器](http://www.boost.org/doc/libs/1_53_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx)。当然,性能权衡,特别是插入。 – juanchopanza 2013-02-15 16:39:11

+0

我不想使用任何外部库。根据我的项目指南,我应该使用C++标准库。 – Manish 2013-02-15 16:40:50

回答

4

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

你可能想看看这些库:

+1

根据数据,可能会减少所需的实际存储量。例如,存储可以使用trie或DAWG的词汇表,导致STXXL的主要节省 – SomeWittyUsername 2013-02-15 16:43:08

+1

+1。适合这种情况。 – leemes 2013-02-15 17:01:23

2

第一种优化将存储data物体代替指针

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

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

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

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

3

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

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替换它。

我还假设你的密钥不是你存储在数据结构中的指针之一。如果是这样,那么一定要摆脱它,并在您的multimap中使用std::pairfirst属性。

根据所存储的数据类型不同,可能会进行进一步的改进。

所以,有很多,可能并不适用于您的数据假设你可以有少这样的:

typedef std::set<std:string> StringMap; 
typedef StringMap::const_iterator StringRef; 
typedef std::multimap<StringRef, std::pair<StringRef, StringRef>> DataMap; 
2

我会怀疑你泄露或不必要的密钥存储复制。关键字char *来自哪里以及如何管理他们的记忆?

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

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

+0

是的,有多余的按键。每个密钥都有22个值,这就是为什么使用multimap。 – Manish 2013-02-16 07:08:40

2

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

而“堆存储的额外数据”并不是真的为我做。在Linux中,内存分配需要大约32个字节的数据。 16字节的开销,分配的内存占用16个字节的倍数。

所以对于存储在多重映射一个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. 

然后我们有char *在多重映射:

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

?? bytes from the file. (Y) 

24 + Y bytes total. 

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

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. 

所以,tha t会为文件中的每个条目创建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个字节。我们可以通过聪明并一次分配数据,值1和值2的内存来节省更多。但这可能有点“太聪明”了。

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)我仍然不确定“密钥”来自multimap的位置,但如果密钥是value1和value2条目之一,那么您可以重用其中的一个,而不是存储另一个副本[假设这是目前的做法]。

对不起,如果这不是一个真正的答案,但我确实相信这是一个答案,“某处,某事在你解释你正在做的事情时不知情”。

了解在程序中分配的内容肯定会有所帮助。