如何在C++中减少数据的内存大小
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
但我注意到内存消耗达到了56GB。我可以知道如何减少内存大小吗?
如果你不能减少实际存储的数据量,你可能想尝试使用一个开销较小的不同容器(map和multimap有相当多的开销),或者找到一种只在内存中保留部分数据的方法。
你可能想看看这些库:
-
STXXL:http://stxxl.sourceforge.net/
-
谷歌CPP Btree:https://code.google.com/p/cpp-btree/
一种可能性是使用std::map<char *, std::vector<data> >
而不是多映射。在多映射中,您将密钥字符串存储在每个条目中。对于映射,您只有一个密钥字符串的副本,并附加多个data
项。
第一个优化是存储data
对象,而不是指针
std::multimap <char*, data, ltstr> m;
因为使用CCD_ 4增加了用于分配的额外存储器开销。
另一种是使用池分配器/内存池来减少动态内存分配的占用空间。
如果您有许多相同的键串,如果您可以重用这些键,那么您也可以改进这一点。
我怀疑您正在泄漏或不必要地复制密钥中的内存。关键char *
字符串来自哪里?如何管理它们的内存?
如果它们与数据对象中的字符串相同,请考虑使用multiset<data *, ltdata>
而不是multimap
。
如果有许多重复的字符串,请考虑在set<char *,ltstr>
中池化字符串以消除重复。
在没有看到一些数据的情况下,有几件事可以提高项目的内存使用率。
首先,正如Olaf所建议的,将数据对象存储在多映射中,而不是指向它的指针。不过,我不建议为数据结构使用池,与直接将其存储在映射中相比,这只会使事情变得复杂,而不节省内存。
不过,您可以为映射指定一个专门的分配器来分配std::pair<char*, data>
对象。这可以节省一些开销和堆碎片。
接下来,您应该关注的主要事项是尝试去掉数据中的两个char*
指针。有14个演出的数据,必须有一些重叠。根据它是什么数据,您可以以不同的方式存储它。
例如,如果数据是名称或关键字,那么将它们存储在中心散列中是有意义的。是的,有更复杂的解决方案,比如上面建议的DAWG,但我认为应该先尝试简单的解决方案。
只需将其存储在std::set<std::string>
中并将迭代器存储到其中,就可以压缩所有重复项,从而节省大量数据。不过,这是假设您没有删除字符串。删除字符串将需要进行一些引用计数,因此您将使用类似std::map<std::string, unsinged long>
的内容。我建议您编写一个继承自/包含此哈希的类,而不是将引用计数逻辑放入您的数据类中。
但是,如果您存储的数据没有太多重叠,例如,因为它是二进制数据,那么我建议您将其存储在std::string
或std::vector<char>
中。原因是现在您可以去掉数据结构中的逻辑,甚至用std::pair
替换它。
我还假设您的键不是存储在数据结构中的指针之一。如果是,那么一定要去掉它,并在多映射中使用std::pair
的first
属性。
根据您存储的数据类型,可能会有进一步的改进。
因此,有了很多可能不适用于您的数据的假设,您可能只有以下几点:
typedef std::set<std:string> StringMap;
typedef StringMap::const_iterator StringRef;
typedef std::multimap<StringRef, std::pair<StringRef, StringRef>> DataMap;
我仍然不完全确定这里发生了什么,但内存开销似乎至少是问题的一部分。然而,总内存消耗大约是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.
多映射的每个节点都有两个指针(我假设它是某种二进制树):
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字节。对于2700万条记录,这一数字略高于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条目之一,那么您可以重用其中一个条目,而不是存储另一个副本(假设目前是这样做的)。
如果这不是一个真实的答案,我很抱歉,但我相信这是一个答案,从某种意义上说,"在你对自己所做事情的解释中,有些事情是不负责任的"。
了解你的计划中进行了哪些分配肯定会有所帮助。
- 在C++中打印指向不同基元数据类型的指针的内存地址
- 如何在 malloc 内存中初始化非 POD 数据
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- 为什么字符串的 move() 会改变内存中底层数据的位置?
- C++,您能否设计一种数据结构,将指针保存在连续内存中并且不会使它们失效?
- 存储在哪个内存段(代码/数据段)类(员工)中?
- 基于浅树的数据结构的内存分配器,用于频繁分配和解除分配
- 获取 R 数据帧的内存地址
- 将数据存储在内存中以供以后访问
- 整数数据如何以位为单位存储在内存中?不是右对齐吗?
- 数据在内存 c++ 中丢失
- 我可以使用哪种数据结构来释放连续内存中的内存?
- 使用 delete [] 运算符取消分配类中数据成员的内存
- 为什么 std::set 容器使用的内存比其数据大小多得多?
- 如果一个变量在它之前释放了另一个(相同的数据类型)变量,如何将其分配给内存?
- 内存映射C++中的流数据
- 如何返回定义良好的内存部分?例如来自图像数据的像素的颜色值
- C++应用程序中的静态数据内存限制
- 基于两个程序之间的事件触发器访问数据(内存)
- C++并集表示数据内存与C标量变量类型