为什么std :: set容器使用的内存多于其数据大小?

时间:2018-08-12 10:52:24

标签: c++ memory set

例如,我们有10 ^ 7个32位整数。将这些整数存储在数组中的内存使用量是32 * 10 ^ 7/8 = 40MB。但是,将10 ^ 7个32位整数插入一组将占用300MB以上的内存。代码:

#include <iostream>
#include <set>

int main(int argc, const char * argv[]) {
    std::set<int> aa;
    for (int i = 0; i < 10000000; i++)
        aa.insert(i);
    return 0;
}

mapunordered_set之类的其他容器在进行类似测试时会占用更多内存。我知道该集合是用红黑树实现的,但是数据结构本身并不能解释高内存使用情况。

我想知道原始数据内存使用率达到5到8倍的原因,以及一些更有效的内存设置的解决方法/替代方法。

1 个答案:

答案 0 :(得分:5)

让我们研究一下GCC中的std :: set实现(在其他编译器中没有太大不同)。 std :: set在GCC上实现为一棵红黑树。每个节点都有一个指向父节点,左节点和右节点的指针以及一个颜色枚举器(_S_red和_S_black)。这意味着,除了int(可能为4个字节)之外,还有3个指针(对于64位系统,为8 * 3 = 24个字节)和一个枚举数(因为它位于_Rb_tree_node_base中的指针之前,因此被填充为8个字节)边界,因此有效地需要额外的8个字节)。

到目前为止,我已经计算出集合中每个整数的24 + 8 + 4 = 36个字节。但是由于节点必须对齐为8个字节,因此必须对其进行填充,以便可以被8整除。这意味着每个节点占用40个字节(比int大10倍)。

但这还不是全部。每个此类节点由std::allocator分配。该分配器使用new分配每个节点。由于delete不知道要释放多少内存,因此每个节点还具有一些与堆相关的元数据。元数据至少应包含已分配块的大小,该大小通常占用8个字节(理论上,可以使用某种霍夫曼编码,并且在大多数情况下仅存储1个字节,但是我从未见过有人这样做) )。

考虑到所有内容,每个int节点的总数为48个字节。这是int的12倍。集合中的每个int所花费的时间比数组或向量所花费的时间多12倍。

您的数字表明您使用的是32位系统,因为您的数据仅占用300 MB。对于32位系统,指针占用4个字节。对于节点中与红黑树相关的数据,这使其成为3 * 4 + 4 = 16字节+对于int的数据为4 +对于元数据的4。每个int总共有24个字节,而不是4个字节。对于大集合而言,这比向量大6倍。数字表明堆元数据占用8个字节,而不仅仅是4个字节(可能是由于对齐约束)。

因此,在您的系统上,预计将占用280MB的空间,而不是40MB(原为std::vector)。

如果要保存一些花生,可以为组使用非标准分配器。您可以通过使用boost的“分离的存储节点”分配器来避免元数据开销。但是就内存而言,这并不是一个巨大的胜利。但是,由于分配器比newdelete中的代码更简单,因此它可以提高性能。