[英]How to reduce memory size for data in C++?
我正在研究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。 我可以知道如何减少内存大小?
如果你不能减少实际存储的数据量,你可能想尝试使用一个开销较小的不同容器(map和multimap有很多)或找到一种方法只保留部分数据记忆。
您可能想看看这些库:
STXXL: http ://stxxl.sourceforge.net/
Google CPP-Btree: https : //code.google.com/p/cpp-btree/
一种可能性是使用std::map<char *, std::vector<data> >
而不是multimap。 在多图中,您将密钥字符串存储在每个条目中。 使用地图,您只有一个密钥字符串副本,并附加了多个data
项。
第一个优化是存储data
对象而不是指针
std::multimap <char*, data, ltstr> m;
因为使用data*
会为分配增加额外的内存开销。
另一个是使用池分配器/内存池来减少动态内存分配的占用空间。
如果您有许多相同的密钥字符串,那么您可以改进它,如果您可以重用密钥。
如果没有看到您的某些数据,有几件事可以改善您项目的内存使用情况。
首先,正如Olaf建议的那样,将数据对象存储在multimap中而不是指向它的指针。 我不建议为您的数据结构使用池,与直接将其存储在地图中相比,它只会使没有内存节省的事情复杂化。
你可以做的是你的地图的一个专门的分配器,它分配std::pair<char*, data>
对象。 这可以节省一些开销和堆碎片。
接下来,您应该关注的主要事情是尝试摆脱数据中的两个char*
指针。 有14个数据,必须有一些重叠。 根据它是什么数据,您可以存储它有点不同。
例如,如果数据是名称或关键字,那么将它们存储在中央散列中是有意义的。 是的,如上所述,有更复杂的解决方案,如DAWG,但我认为应首先尝试简单的解决方案。
通过简单地将它存储在std::set<std::string>
并将迭代器存储到它,您将压缩所有重复项,这将节省大量数据。 这假设你没有删除字符串。 删除字符串将需要您进行一些引用计数,因此您将使用类似std::map<std::string, unsinged long>
。 我建议您编写一个继承自/包含此哈希的类,而不是将引用计数逻辑放入您的数据类中。
如果您存储的数据没有很多重叠,例如因为它是二进制数据,那么我建议您将其存储在std::string
或std::vector<char>
。 原因是因为现在你可以摆脱数据结构中的逻辑,甚至用std::pair
替换它。
我还假设您的密钥不是您存储在数据结构中的指针之一。 如果是,绝对摆脱它并在你的multimap中使用std::pair
的first
属性。
根据您存储的数据类型,可能会进一步改进。
因此,有很多假设可能不适用于您的数据,您可以尽可能少:
typedef std::set<std:string> StringMap;
typedef StringMap::const_iterator StringRef;
typedef std::multimap<StringRef, std::pair<StringRef, StringRef>> DataMap;
我怀疑你是否泄漏或不必要地重复密钥中的内存。 关键char *
字符串来自何处以及如何管理其内存?
如果它们与数据对象中的字符串相同,请考虑使用multiset<data *, ltdata>
而不是multimap
。
如果有许多重复的字符串,请考虑在set<char *,ltstr>
汇集字符串以消除重复。
我仍然不完全确定这里发生了什么,但似乎内存开销至少是问题的一部分。 但是,总体内存消耗约为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
项,并一次一个地输出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条目之一,那么你可以重用其中一个,而不是存储另一个副本[假设它是如何的目前完成]。
我很抱歉,如果这不是一个真正的答案,但我确实认为这是一个答案,在某种意义上说“在某个地方,在你解释你正在做的事情时,有些事情是不明智的”。
了解在您的程序中进行的分配肯定会有所帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.