Max*_*hon 5 c++ memory memory-management fragmentation dynamic-memory-allocation
我正在开发一个 C++ 应用程序,该程序会无限地运行,随着时间的推移分配和释放数百万个字符串 (char*)。RAM的使用是程序中需要认真考虑的因素。这会导致 RAM 使用率随着时间的推移越来越高。我认为问题在于堆碎片。我真的需要找到一个解决方案。
您可以在图中看到,在程序中进行数百万次分配和释放之后,使用量只是在增加。从我测试它的方式来看,我知道它存储的数据并没有增加。我猜你会问,“你怎么确定这一点?”,“你怎么确定这不仅仅是内存泄漏?”,好吧。
这个测试运行的时间要长得多。只要有可能,我就会malloc_trim(0)在我的程序中运行。看来,应用程序最终可以将未使用的内存返回给操作系统,并且它几乎为零(我的程序当前拥有的实际数据大小)。这意味着问题不是内存泄漏。但我不能依赖这种行为,我的程序的分配和释放模式是随机的,如果它从不释放内存怎么办?
GCC 11 / G++ 11编译器。如果某些旧版本的分配器不好。我不应该有这个问题。psutil模块。proc.memory_full_info()[0],这给了我RSS.所以我的问题是,在一个随着时间的推移发生数百万次分配和释放的应用程序中,程序员应该做什么(我应该做什么),并且它们的大小不同,因此内存池很难有效使用。我无法改变程序的作用,我只能改变实现细节。
赏金编辑:当尝试使用内存池时,是否可以创建多个内存池,以便每个可能的字节数都有一个池?例如,我的字符串可能在 30-4000 字节之间。因此,有人不能4000 - 30 + 1为程序的每个可能的分配大小创建 3971 个内存池吗?这不适用吗?所有池都可以从小规模开始(不会损失太多内存),然后扩大,以在性能和内存之间取得平衡。我并不是想利用内存池预先保留大空间的能力。由于频繁的分配和释放,我只是想有效地重用释放的空间。
最后编辑:事实证明,图表中出现的内存增长实际上来自我程序中的 http 请求队列。我没有看到我所做的数十万次测试使这个队列变得臃肿(类似于 webhook)。图 2 的合理解释是,我最终从服务器禁止了 DDOS(或者由于某种原因无法再打开连接),队列清空,RAM 问题解决。因此,以后阅读这个问题的任何人,请考虑每种可能性。我从来没想过会发生这样的事情。不是内存泄漏,而是实现细节。我仍然认为@Hajo Kirchhoff 值得赏金,他的回答确实很有启发性。
如果一切确实如您所说,并且没有您尚未发现的错误,那么请尝试以下操作:
无论如何,malloc 和其他内存分配通常使用 16 字节的块,即使实际请求的大小小于 16 字节。所以你只需要 4000/16 - 30/16 ~ 250 个不同的内存池。
const int chunk_size = 16;
memory_pools pool[250]; // 250 memory pools, managing '(idx+1)*chunk_size' size
char* reserve_mem(size_t sz)
{
size_t pool_idx_to_use = sz/chunk_size;
char * rc=pool[pool_idx_to_use].allocate();
}
Run Code Online (Sandbox Code Playgroud)
IOW,你有 250 个内存池。pool[0]分配并管理长度为16字节的chunk。pool[100] 管理 1600 字节等的块...
如果您提前知道字符串的长度分布,则可以根据此知识为池保留初始内存。否则我可能会以 4096 字节的增量为池保留内存。
因为虽然 malloc C 堆通常以 16 字节的倍数分配内存,但它会(至少在 Windows 下,但我猜测 Linux 也类似)向操作系统请求内存 - 通常适用于 4K 页。IOW,由操作系统管理的“外部”内存堆保留并释放 4096 字节。
因此,将您自己的内部内存池增加到 4096 字节意味着操作系统应用程序堆中不会出现碎片。这个 4096 页大小(或倍数...)来自处理器架构。Intel 处理器的内置页面大小为 4K(或其倍数)。不知道其他处理器,但我怀疑那里有类似的架构。
所以,总结一下:
每个内存池的字符串使用 16 字节倍数的块。使用 4K 字节倍数的块来增加内存池。
这将使应用程序的内存使用与操作系统的内存管理保持一致,并尽可能避免碎片。
从操作系统的角度来看,您的应用程序只会以 4K 块的形式增加内存。这非常容易分配和释放。而且不存在碎片。
从内部 (lib) C 堆管理的角度来看,您的应用程序将使用内存池,并且每个字符串最多浪费 15 个字节。此外,所有类似的长度分配都将堆在一起,因此也不会产生碎片。