STL容器的内存消耗

11

我正在开发一个应用程序,计划使用几个STL容器。如果内存占用达到一定阈值,应用程序将执行某些步骤。为此,我需要对STL容器使用的内存进行接近精确的计算。

vector<string> StringList
map<string, int> mapstring

这是我估算内存的方法:

对于 StringList 的大小,遍历向量的所有元素并累加字符串的大小。

string size = sizeof(string) + string.capacity()*sizeof(char)

最后,将 sizeof(StringList); 添加到其中。

对于 mapstring 的大小,循环遍历容器的所有键,并不断添加字符串大小,然后再添加 int 的大小,即 mapstring.size()*sizeof(int)。最后,将 sizeof(mapstring); 添加到其中。

我想更好的方法是指定自己的分配器类并在其中跟踪内存使用情况,但编写一个可能会比较困难。这个估计看起来还不错吗?


1
类似的问题:https://dev59.com/p3I95IYBdhLWcg3wxA8- https://dev59.com/d3RB5IYBdhLWcg3wF0HH这是一个合理的方法来获得一个粗略的下限,但可能有比你想象的更多的开销。例如,std::map元素必须包含各种额外的指针和与保持平衡相关的信息... Valgrind工具massif跟踪堆分配,并可能对分析程序的内存使用有所帮助。 - BoBTFish
这也取决于你使用的是哪个STL实现,例如STLPort或者gcc STL。 - weima
3
我猜更好的方法是指定自己的分配器类,并在其中跟踪内存使用情况,但编写一个可能并不容易。-- 这确实是最好的方法,比对STL实现进行猜测要好得多。 - DevSolar
你不能直接询问操作系统进程使用了多少内存吗? - jalf
通常情况下,一个应用程序不会消耗它所使用的任何内存。 - moooeeeep
2个回答

11
一个 std::vector<element> 通常占用总共 3 个机器字 加上 sizeof(element) * capacity() 的内存。对于典型的实现,开销包括指向向量开头、结尾和当前大小的指针。元素本身存储在连续的内存中。capacity() 通常有两倍的空间可容纳实际元素数量。
一个 std::map<element, int> 通常占用总共 2 个机器字 加上每个元素的 3 个机器字 加上 [ sizeof(element) +sizeof(int) ] * num_elements 的内存。对于典型的实现,开销包括指向存储元素的指针。元素本身存储在二叉树中,其中还有指向其父母和两个孩子的指针。
按照这些经验法则,您只需要知道每个字符串的平均字符数以及总字符串数,就可以知道总内存消耗。

对于 std::vector,应该使用 capacity 而不是元素数量。如果 std::map 使用池分配器(很多都是这样),那么它可能会分配比 map 中的元素更多的节点。 - James Kanze
@JamesKanze 谢谢,已更新容量(capacity())备注。那么对于 std::map,有没有一种非侵入式的方法(即不记录分配器)来获取内存消耗? - TemplateRex
如果你不知道实现方法,那就没有办法;如果分配器使用节点池,可能根本没有办法。这里最简单的解决方案就是用跟踪分配的方式替换全局的 operator newoperator delete。(默认的分配器需要使用 ::operator new 来获取它们分配的内存。) - James Kanze
此外,你关于 capacity() 的评论,即其可以容纳多达实际元素数量两倍的空间是不正确的。除了增长倍数之外,没有任何限制。通常的倍数为1.5和2,但更大(或更小)的倍数也是允许的。 - James Kanze
@JamesKanze 我多次提到“典型实现”,因为没有强制的实现方式。 - TemplateRex
指数增长是必须的。我猜1.5是最常见的因子,2是紧随其后的。 - James Kanze

11
对于std::vectorstd::string,容量而非大小会更好地近似。对于基于节点的容器(例如std::set等),您需要将节点数量(大约是元素数量)乘以每个节点的大小。但这仅在分配器不使用优化的池分配器来为节点分配空间时才准确。
然而,如果您真的想知道正在使用多少内存,更好的策略是替换全局的operator newoperator delete,并跟踪实际的分配情况。更准确的方法是替换mallocfree。严格来说这不被允许,但实际上,我从来没有遇到过无法使用该方法的实现。另一方面,如果您替换mallocfree,则必须自己实现实际的内存管理。如果您替换operator newoperator delete,则可以使用mallocfree,这使得它相当简单。
请注意,每个分配都有一些固定的开销。100000个每个10字节的分配将消耗比10个每个100000字节的分配更多的内存。

替换new将会给我提供堆分配期间使用的内存。对于这个使用量,我需要添加所有容器的开销,例如sizeof(vector) + sizeof(string)等。 - Frank Q.
这取决于您要测量什么。在C++中替换new将使您使用动态内存(但不适用于您可能正在使用的任何C部分)。这就是我所理解的“内存消耗”。它不会计算堆栈上的局部变量std::vector中的三个指针,但它将计算此类向量中所有字符串使用的所有内存(因为向量内容的所有内存都是动态分配的)。 - James Kanze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接