简体   繁体   English

使用 malloc 和 realloc 来使用 C 结构,但占用的内存空间是我计算的两倍

[英]use malloc and realloc to use C struct but take twice the memory space as I calculated

I use C struct to build a prefix tree, ie Trie.我使用C struct 来构建前缀树,即Trie。 I want to use it to store a lot of number lists to get the next possible number in a given path.我想用它来存储大量数字列表,以获取给定路径中的下一个可能数字。 (For example, I have [10, 15, 17] and [10, 15, 18], then the next possible number of [10,15] is 17,18.) The problem I met is about the memory use, my each struct Trie node takes only 12 bytes(I used sizeof() to check it), and have 0.83 billion nodes in total, which should take 0.83 billion * 12 bytes = 10G memory use, but actually I used 20G memory, and I want to reduce the memory use into 10G. (比如我有 [10, 15, 17] 和 [10, 15, 18],那么下一个可能的 [10,15] 数是 17,18。)我遇到的问题是关于内存使用,我的每个 struct Trie 节点只占用 12 个字节(我使用 sizeof() 来检查它),总共有 8.3 亿个节点,应该占用 08.3 亿 * 12 个字节 = 10G 内存使用,但实际上我使用了 20G 内存,我想要将内存使用减少到10G。

I use an unsigned short to store the data for each node, unsignedn short n_child to store how many children does this node have, a pointer to his children list beginning location, and realloc a 12 bytes bigger memory space for a new node.我使用一个 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;

When I have to add a new child, I use:当我必须添加一个新孩子时,我使用:

this_node->n_child = this_node->n_child + 1;
this_node->children = (Trie *)realloc(this_node->children, this_node->n_child * sizeof(Trie));

I want to know why the memory use is bigger than calculated and cound I reduce the memory use.我想知道为什么内存使用量大于计算值,并且我减少了内存使用量。

The problem here is that you are allocating very small chunks of data (the size of struct trie_node is very small, and it's about the size of the data needed by the malloc() library to manage the different allocations you do. And you call realloc() each time you add a single element. Figure this scenario, you have a chunk with, let's say 10 nodes and you add one, reallocing from 10 to 11, but as you have allocated plenty of different arrays, you don't have space to fit in the hole you have your last array, so the memory manager has to make a free hole with space for 10 nodes and allocate another with space or 11 (elsewhere).这里的问题是您正在分配非常小的数据块( struct trie_node的大小非常小,它大约是malloc()库管理您所做的不同分配所需的数据大小。并且您调用realloc()每次添加一个元素。想象一下这个场景,你有一个块,假设有10节点,然后你添加一个,从 10 重新分配到 11,但是由于你分配了很多不同的数组,你没有空间以适合您拥有最后一个数组的孔,因此内存管理器必须为 10 个节点创建一个空闲孔,并分配另一个具有空间或 11(其他地方)的空间。

If you have the luck of having another chunk with 9 nodes and growing to 10, then you can reuse the last hole.... but that's not normally the case, that hole is reused (partially) before there's another need to grow from 9 to 10. and this provokes that there's much fragmentation in the dynamic memory area.如果您有幸拥有另一个具有 9 个节点并增长到 10 个节点的块,那么您可以重用最后一个孔……但通常情况并非如此,在另一个需要从 9 增长之前(部分)重用该孔到 10. 这会引起动态内存区域中有很多碎片。

The fragmentation, jointly with the fact that you use a very small node structure, is generating this overhead of 100% of the used memory.碎片以及您使用非常小的节点结构这一事实正在产生 100% 已用内存的开销。

You can alleviate this in several ways:您可以通过多种方式缓解这种情况:

  • Don't realloc() by one.不要一个一个地重新realloc() Just do it doubling, for example.例如,只需将其加倍即可。 This has two advantages: first, the chunks sizes are only powers of two, so the fragmentation level is far less, because the probability of having a valid power of two is far easier if you only have powers of two.这有两个优点:首先,块大小只有 2 的幂,因此碎片级别要低得多,因为如果您只有 2 的幂,则具有有效 2 幂的概率要容易得多。 Another good value is to grow by adding the last two used sizes (as in a fibonacci series).另一个很好的价值是通过添加最后两个使用的大小来增长(如在斐波那契数列中)。

  • Add a pointer to the sibling and organise your tree as linked lists... This way you will allocate all chunks the same size (memory managers are best when the sizes are the same) But be careful, you grow your structure by one pointer.添加一个指向同级的指针并将树组织为链表......这样你将分配相同大小的所有块(内存管理器在大小相同时最好)但是要小心,你的结构增加了一个指针。 Parto of what you get from one side, goes out in the other.你从一侧得到的一部分,在另一侧消失。

  • If you know in advance the average of the number of children a node will have, preallocating that average number will be interesting as it will make the chunks to be near this fixed size, so it will manage better.如果您事先知道一个节点将拥有的子节点数的平均值,那么预先分配该平均数会很有趣,因为它会使块接近这个固定大小,因此它会更好地管理。

Finally, there's one thing you cannot avoid, and is to have some overhead, due to the metadata the memory manager needs to allocate, apart from your data, to manage properly the heap.最后,您无法避免的一件事是有一些开销,因为内存管理器需要分配元数据(除了您的数据)以正确管理堆。 But the bigger are your allocations, the lower losses on this data, as the memory managers normally need some amount of data per allocation, and it doesn't depend on the allocation size.但是您的分配越大,这些数据的损失就越小,因为内存管理器通常每次分配需要一定数量的数据,并且它不依赖于分配大小。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM