c++ - 为什么 std::set 容器使用的内存比其数据大小多得多?

标签 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 等其他容器在类似测试中占用更多内存。我知道set是用红黑树实现的,但是数据结构本身并不能解释内存占用高的问题。

我想知道这 5 到 8 倍原始数据内存使用量背后的原因,以及一些更高效内存集的解决方法/替代方案。

最佳答案

让我们检查一下 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 不知道要释放多少内存,每个节点也有一些与堆相关的元数据。元数据至少应该包含分配 block 的大小,通常需要 8 个字节(理论上,可以使用某种霍夫曼编码,在大多数情况下只存储 1 个字节,但我从未见过有人这样做).

综合考虑,每个 int 节点的总大小为 48 字节。这是 int 的 12 倍。集合中的每个 int 比它在数组或 vector 中花费的时间多 12 倍。

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

因此在您的系统上,预计需要 280MB,而不是 40MB(如果是 std::vector)。

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

关于c++ - 为什么 std::set 容器使用的内存比其数据大小多得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51808110/

相关文章:

c++ - 使用 INF 文件 c++ 以编程方式安装驱动程序

c++ - 无法通过 CHttpFile 发送 POST 请求 - C++/MFC

c++ - C++ 中的 "As a rule of thumb, make all your methods virtual"- 合理的建议?

android - 带有许多小图像的游戏中出现 OutOfMemoryError

.net - .NET 是否有与 Python 的 issubset 方法等效的方法?

C++ - Visual Studio - 缺少类型说明符 - 假定为 int

c++ - OpenCV Mat 中的动态内存释放错误

c++ - 在我调用 delete 之后仍然可以访问值,c++

java - 存储字符串且忽略字母大小写的数据结构

python - 从列表列表中删除子列表