C++动态内存管理问题求解

2022-06-29 11:30:17 +08:00
 ligiggy

项目上需要处理若干组,每组 500M 左右的数据,数据组成是大概可以理解为 3 个 std::vector<float>,一个 std::vector<structA>( structA 为自定义结构体),每处理一组数据就需要释放掉。

数据处理大概包括:插值,平移等。

由于载入内存比较大,导致处理的时间越来越长,内存越来越碎片化。

找了几个内存池的解决方案,好像不是很好解决我的问题。比如 boost::pool ,std::allocator ,使用起来都比较麻烦,比如 boost ,很多释放都是静态的,allocator 的话,基本上需要重新造轮子。后面发现 c++17 添加了 pmr::monotonic buffer resource ,尝试 debug 几次之后,发现在现在的机器上一次只能分配 100M 的内存,200M 和 500M ,都会在运行的时候崩溃掉,应该是没有那么多的连续内存了,想问下大佬们,有什么推荐的解决方案(轮子)吗?

我期望中的解决方案其实与 pmr 的预期类似,就是我申请一块足够大的连续内存,让这块内存分配数据的存储空间,处理完后,直接将整块内存释放掉即可。如果没有联系的内存,也可以分配成几个 100M ,几个 50M ,几个 20M 这样子的,也会比完全碎片化的要快。

4237 次点击
所在节点    C++
43 条回复
liuhan907
2022-06-29 11:33:39 +08:00
std::vector 分配的内存本来不就是连续的么?
ligiggy
2022-06-29 11:35:44 +08:00
@liuhan907 是连续的。
luassuns
2022-06-29 11:38:06 +08:00
每组数据就很大了,用之前用 vector.reserve 扩展一下避免分配
ligiggy
2022-06-29 11:43:09 +08:00
@luassuns 我读出数据之前已经 resize 过了。
shylockhg
2022-06-29 11:44:54 +08:00
感觉楼上是正解,reserve 够大就行,你这个需求和内存池没关系
ipwx
2022-06-29 11:55:56 +08:00
"就是我申请一块足够大的连续内存,让这块内存分配数据的存储空间"

其实标准库也是这么干的。如果标准库不能满足你的需求,你应该对 new / delete 之类的操作进行优化(侵入式),而不是找个新的什么内存池。
ipwx
2022-06-29 11:57:29 +08:00
一般在算法意义上的内存池都是“只用不扔”的。比如申请一大段内存,不断切出来新的 node 做树结构之类的。哪怕要回收,也是做一个很简单的链表,把刚刚不用的节点直接串起来。只要链表上有节点就不切新的,而是用原来的。
hackfly
2022-06-29 12:01:06 +08:00
内存碎片就是因为不断的申请、析放产生的,要减少申请,增加重复利用。
GeruzoniAnsasu
2022-06-29 12:17:45 +08:00
500m 是很大了,而且比较要命的是,如果你只用标准库而不直接调 OS API 预留内存的话,上一个 500m 的连续空间不知道什么时候就会被 glibc(ptmalloc)拆碎,拆碎了之后还不会优先合并大区块或者热区块,让 glibc 来管这些反复申请的大空间不是个好做法

monotonic buffer 是单调递增 buffer (即指针永不回退,除非整个 buffer 释放),适用于把相对大的一整块内存分给要不停分配小空间折腾的场合,不太符合你的需求


建议尝试 std::pmr::vector 配合 pmr::memory_resource ,然后手动 mmap 一块内存作为 memory_resource 的基底

另外因分配内存导致的性能缓慢通常是因为分配姿势不对引起的,比如是不是用循环依次复制每个对象了?是不是调用太多其实没用的构造 /析构了? c++里如果发现分配内存占了时间大头绝大多数都是逻辑写得有问题可以优化掉的
nightwitch
2022-06-29 12:21:49 +08:00
可以链接上 tcmalloc 试试。

不过按你的描述来说你是已经 reserve 过了,相当于申请和释放都是在操作大块内存,没道理会产生很多内存碎片。
最好还是用 profiler 看一下性能瓶颈在哪里。
ligiggy
2022-06-29 12:33:23 +08:00
@GeruzoniAnsasu
感谢解答,目前我是这样做的。

std::array<std::byte, 102400> buffer;
std::pmr::monotonic_buffer_resource resource{buffer.data(), buffer.size()};
std::pmr::vector<Frame> vecFrame{&resource};

后面我尝试下你说的几点问题。
ligiggy
2022-06-29 12:35:37 +08:00
@nightwitch 好的,感谢提的建议。
L4Linux
2022-06-29 13:40:25 +08:00
[un]synchronized_pool_resource with max_blocks_per_chunk
lakehylia
2022-06-29 14:05:57 +08:00
如果用到的内存比较平均,那你就预估一个内存块的最大值,申请好之后,用完了之后先不要释放,下次在用直接用用过的
ipwx
2022-06-29 14:11:30 +08:00
我觉得 9L 说得对,楼主的需求适合用 mmap 手动切一整块出来用。

mmap 的内存单位大小一般是 4K 。计算你要 12B 也会给你切出来 4K 。好处是保证没有碎片。事实上 malloc / new 很可能是低下用 mmap 切出来了这种块然后自己切着玩的。
ipwx
2022-06-29 14:12:06 +08:00
... 顺便 mmap 大概是肯定不会有碎片的。因为在内核中 mmap 是要写到 cpu 的页表里的。
ipwx
2022-06-29 14:13:16 +08:00
另外也永远不需要担心 mmap 切出来的若干 4K 的东西是不连续的。逻辑地址上 mmap 永远可以是连续空间,只不过 cpu 的页表可以把逻辑连续的若干个 4K 映射到物理不连续的 4K 。这一切都发生在内核态对用户程序透明。
boaofCHIAN
2022-06-29 15:39:12 +08:00
试试直接用 tcmalloc 直接接管内存分配和使用吧
ligiggy
2022-06-29 15:52:51 +08:00
@boaofCHIAN 很遗憾,我用的 msvc
cs8425
2022-06-29 16:41:10 +08:00
不负责任丢一个内存分配库
楼主可以试试...?
https://github.com/microsoft/mimalloc

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/862885

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX