跳转至

python内存异常的一次排查

在使用rust通过ffi运行cpython的解释器的时候, 在进行压力测试结束之后, 内存并没有被回收, 还以为是内存泄漏, 这隔着ffi就像是一辆安全稳固的沃尔沃拉着一辆大众~, 这种runtime出现问题, debug工具已经完全不能用了, 遂只能根据经验减少变量进行排查.

在python runtime 初始后准备解释执行时内存占用为30m

500个python线程, 每个线程都有 li = [i for i in range(num)] 这样的代码

虽然都会进行gc清除资源但是内存并没有释放

当并发的执行的时候, 最后会有300m的这些没有被回收的内存

变量不变, 只改变运行方式, 500个线程串行运行, 最后剩余内存只为50m, 和30m相差不大

情况很了然, 并发执行的时候 发生了泄漏, 串行没有, 说明什么, 说明不一定是发生了泄漏, 思考了一下这个是不是由系统造成的, 虽然资源释放了, 但是并发执行的时候, 500个线程同时创建list并往里面塞入数据, 线程结束后, 但是内存并没有被回收, 想一想 cpython在申请内存的时候用malloc和free管理堆上资源, 那这样就清楚了, 当解释器进程申请内存是向内存分配器申请, 而内存分配器又向操作系统申请内存, 这种开销比较大, 要建立页表之类的, 操作系统一般直接分配一个"块"给内存管理器, 避免频繁的向操作系统申请/释放内存, 以减少系统调用次数, 因此free资源之后, 这些资源归还了程序所申请的堆到内存管理器, 这部分没有归还的资源, 当再次new或者malloc申请内存就"可能"会重用, 避免了系统调用, glibc下的内存管理器(ptmalloc), 可以说是一个内存池, 应用程序的申请内存或者释放内存, 都是在该内存池中实现,从内存池申请到的内存会被归还到内存池中, 只有满足ptmalloc的某些特定条件之后, ptmalloc才会调用sys_trim函数, 将内存归还操作系统, Linux 堆使用 brk 模式从系统分配到的内存是连续的, 但凡有一个没 free 的内存占着堆顶附近, 整段内存就释放不掉

500个线程并发的执行同时申请内存, 就导致了一下进行了500次系统调用, 同时持有了这些内存, 所以在free之后, 500个线程申请的内存 中间还涉及到了pyhton的runtime, 并没有被立即释放

注意

上文中存在一些错误, py使用的Pymalloc进行内存管理的, 而不是libc里面的malloc(虽然pymalloc最后还是用的malloc)