c - 使用 malloc 和 realloc 来使用 C 结构,但占用的内存空间是我计算的两倍
问题描述
我使用C struct 来构建前缀树,即Trie。我想用它来存储很多号码列表,以获取给定路径中的下一个可能的号码。(例如,我有 [10, 15, 17] 和 [10, 15, 18],那么 [10,15] 的下一个可能数量是 17,18。)我遇到的问题是关于内存使用的,我的每个 struct Trie 节点只占用 12 字节(我用 sizeof() 来检查),总共有 8.3 亿个节点,应该占用 8.3 亿 * 12 字节 = 10G 内存使用,但实际上我使用了 20G 内存,我想要将内存使用减少到10G。
我使用 unsigned short 来存储每个节点的数据,unsignedn short n_child 来存储这个节点有多少孩子,一个指向他的孩子列表开始位置的指针,并为新节点重新分配 12 字节的内存空间。
#pragma pack(2)
typedef struct trie_node{
unsigned short data;
unsigned short n_child;
struct trie_node * children;
} Trie;
当我必须添加一个新孩子时,我使用:
this_node->n_child = this_node->n_child + 1;
this_node->children = (Trie *)realloc(this_node->children, this_node->n_child * sizeof(Trie));
我想知道为什么内存使用量比计算的大,我可以减少内存使用量。
解决方案
这里的问题是您分配了非常小的数据块(大小struct trie_node
非常小,并且大约是malloc()
库管理您所做的不同分配所需的数据大小。realloc()
每次添加单个想象一下这个场景,你有一个块,比如说10
节点,你添加一个,从 10 重新分配到 11,但是由于你分配了很多不同的数组,你没有空间来容纳你的洞最后一个数组,因此内存管理器必须为 10 个节点创建一个可用空间,并分配另一个空间或 11 个(其他地方)。
如果您有幸拥有另一个具有 9 个节点并增长到 10 个的块,那么您可以重用最后一个洞......但通常情况并非如此,在另一个需要从 9 增长之前,该洞被(部分)重用到 10. 这会导致动态内存区域有很多碎片。
碎片以及您使用非常小的节点结构这一事实正在产生 100% 的已用内存开销。
您可以通过多种方式缓解这种情况:
不要
realloc()
一个。例如,只需将其加倍。这有两个优点:首先,块大小只有 2 的幂,因此碎片级别要低得多,因为如果只有 2 的幂,则具有有效的 2 幂的概率要容易得多。另一个好的价值是通过添加最后两个使用的大小来增长(如在斐波那契数列中)。添加指向同级的指针并将树组织为链表...这样您将分配相同大小的所有块(内存管理器在大小相同时最好)但是要小心,您的结构会增加一个指针。你从一侧得到的部分,在另一侧出去。
如果您事先知道节点将拥有的子节点数量的平均值,那么预先分配该平均数量将会很有趣,因为它会使块接近这个固定大小,因此它会更好地管理。
最后,您无法避免一件事,那就是有一些开销,因为除了您的数据之外,内存管理器需要分配元数据来正确管理堆。但是您的分配越大,这些数据的损失就越低,因为内存管理器通常每次分配都需要一定量的数据,并且它不依赖于分配大小。
推荐阅读
- regex - 在 sed 中对同一匹配执行多个命令
- uibutton - 在 IBAction 闭包中禁用 UIButton - Swift 4
- android - Kotlin 中语法 `!::someReference` 的等价物
- php - 将时间戳转换为 JSON 格式的 PhP 代码
- sql - 铸造货币时删除尾随零
- ios - 使用 UILocalizedIndexedCollation 在 Swift 中创建节索引时,是否有匹配 CNContactSortOrder.userDefault 的选择器?
- c++ - 如何将数组的地址分配给指针?
- python-3.x - 使用 panda python 的样本数 -5 必须为非负数
- selenium - 使用 Selenium Grid 进行数据驱动测试
- macos - 无法通过 macOS 中的 ansible unarchive 模块提取 tar 文件?