V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
iyaozhen
V2EX  ›  Python

大佬们,有谁研究过 Python importlib 机制? sys.modules 缓存太大了

  •  
  •   iyaozhen ·
    iyaozhen · 2022-03-01 15:27:27 +08:00 · 4289 次点击
    这是一个创建于 998 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有个业务需要动态加载很多本地文件( pb 生成的)

    Python 自己的 sys.modules 缓存,加载过的都会缓存。而且是多级
    比如一个模块是 a.b.c ,会生成 a a.b a.b.c 缓存

    每个 request 是共用这个,会让 sys.modules 这个缓存越来越大,线上 20 来个实例,一共占用了 700 多 G 内存

    https://docs.python.org/zh-cn/3/reference/import.html#the-module-cache
    第 1 条附言  ·  2022-03-02 16:38:28 +08:00
    稍等解释下为什么这么大
    1. 动态加载的是 pb 生成的 xxx_pb2.py 文件,熟悉的同学都了解,pb 生成的代码冗余信息很多,很大,甚至 pb 源文件内容都包含在里面,我大概看了下生成的文件大的有 2.4M 小的几十 k 。这只是 py 文件的大小,再加载到内存里应该更大(没实际算过)
    而且 Python 加载机制不只是 sys.modules ,别的地方也有占用

    2. 服务运行模式选的不对,服务启动的时候开了 10 个 worker 进程,会导致占用大了 10 倍,其实开 1 个 worker 进程就行
    27 条回复    2022-03-02 16:25:30 +08:00
    keepeye
        1
    keepeye  
       2022-03-01 15:39:19 +08:00
    帮顶,试试 importlib.invalidate_caches() 不知道是不是用来清缓存的
    iyaozhen
        2
    iyaozhen  
    OP
       2022-03-01 15:51:10 +08:00
    @keepeye
    importlib.invalidate_caches()
    Invalidate the internal caches of finders stored at sys.meta_path. If a finder implements invalidate_caches() then it will be called to perform the invalidation. This function should be called if any modules are created/installed while your program is running to guarantee all finders will notice the new module’s existence.

    这个主要是发现新模块的,比如 a.b.c 多了个 a.b.c1

    实际试了下也不会清 sys.modules
    ruanimal
        3
    ruanimal  
       2022-03-01 15:54:11 +08:00   ❤️ 1
    https://stackoverflow.com/questions/3105801/unload-a-module-in-python

    应该是不行的,感觉服务拆分下吧
    iyaozhen
        4
    iyaozhen  
    OP
       2022-03-01 15:58:44 +08:00
    @ruanimal 拆分也没用吧 总量不会变。耗内存
    hhhhhh123
        5
    hhhhhh123  
       2022-03-01 17:28:30 +08:00
    用一个删一个 都在一个字典里面, 这样就行了, 我一起优化过一个服务刚好就是 7 ,8 k 得根据入参导入不同得文件 然后获取里面得函数 进行调用 一样得道理 , 用完即删除 无非是用时间换空间
    zhengxiaowai
        6
    zhengxiaowai  
       2022-03-01 17:57:04 +08:00
    import 有个 hook ,感觉可以搞一下,盲猜一下思路大约是,启动不加载那些生成的 pb ,只有在用到的时候利用 hook 捕获一下,然后做一下动态导入,同时这个 hook 里还有一个 lru ,比如 500 个,超出 500 个的从 sys.module 中给他扔掉。

    ------

    BTW, builder 这个服务不用太管它,,偶尔还会有内存泄露问题 :-)
    joApioVVx4M4X6Rf
        7
    joApioVVx4M4X6Rf  
       2022-03-01 20:32:34 +08:00
    楼主解决了吗
    alphanow
        8
    alphanow  
       2022-03-01 21:39:50 +08:00 via Android   ❤️ 1
    sys.modules 只是一个 Python 引用列表,真正的对象是在堆记得,除了 Python 代码本身,底层的 C 代码有时也会对其存在引用。所以直接删除里面的条目可能是不起作用的。
    所以有两个可能的解决方案:
    用文件读取处理的方式生成一个对象,避免 import
    开一个独立的 process 处理数据,用完直接干掉
    iyaozhen
        9
    iyaozhen  
    OP
       2022-03-01 21:47:47 +08:00
    @hhhhhh123 这样细节上不好操作
    特别是请求量大的时候,比如 a.b.c1 a.b.c2 cache 里面有 a.b ,如果 a.b.c1 后删除 a.b cache ,刚好 a.b.c2 内部在用的时候取不到了
    imn1
        10
    imn1  
       2022-03-01 21:48:54 +08:00
    700G 内存,羡慕
    iyaozhen
        11
    iyaozhen  
    OP
       2022-03-01 21:52:53 +08:00
    @zhengxiaowai 哈哈哈,老哥 你这留下的坑,请打开飞书交流

    嗯嗯 hook 的思路想过,还得具体试试
    iyaozhen
        12
    iyaozhen  
    OP
       2022-03-01 21:53:21 +08:00
    @v2exblog 还没呢 得先多想几个解决方案
    iyaozhen
        13
    iyaozhen  
    OP
       2022-03-01 22:01:21 +08:00
    @alphanow 「用文件读取处理的方式生成一个对象」这是什么操作,但可能也不行 因为文件内部还有 import 嵌套

    「开一个独立的 process 处理数据,用完直接干掉」改成多进程模型,这倒好像可以,但其实就用不上缓存了,不知道性能如何
    Cooky
        14
    Cooky  
       2022-03-01 22:01:22 +08:00
    整个多进程,让子进程处理,处理完了关了重开?
    iyaozhen
        15
    iyaozhen  
    OP
       2022-03-01 22:02:01 +08:00
    @imn1 运维已经要从我工资里面扣了 (开玩笑
    iyaozhen
        16
    iyaozhen  
    OP
       2022-03-01 22:02:57 +08:00
    @Cooky 我试试
    flynaj
        17
    flynaj  
       2022-03-01 22:13:55 +08:00 via Android
    要性能你上 golang,python,,
    sujin190
        18
    sujin190  
       2022-03-01 22:30:10 +08:00 via Android
    每个实例将近 40G 内存,如果是模块占用内存多那真是好奇你这些 module 都用来干嘛了,几十 G 的代码文件啊,再说 python 的模块缓存好像是按代码文件纬度来的吧,c 模块应该是 so 级别的,你确定不是你 module 直接引用加载数据了,代码文件这么大,有点不科学
    009694
        19
    009694  
       2022-03-01 22:55:03 +08:00 via iPhone
    fork 一个新进程去加载和计算你需要的动态库 用完即丢 。 动态需求要有动态的思路
    ipwx
        20
    ipwx  
       2022-03-01 23:13:52 +08:00
    @iyaozhen 来自 php 时代的 trick ( php-fpm ):后台进程负责 import cache ,过一段时间就杀死。这样就能从头再来了。
    ipwx
        21
    ipwx  
       2022-03-01 23:15:03 +08:00   ❤️ 1
    @sujin190 我这里也有个类似的需求,Python 在线生成 .so 然后 import 做计算的。.so 是根据上传的文本产生 C++ 代码然后编译出来 python 模块。。。所以用了个 subprocess
    24owls
        22
    24owls  
       2022-03-01 23:23:24 +08:00
    看了 #3 给的问题,import 进来的 module 无论如何也没法清除干净,那么想要清除干净就只能从一开始就不按照 importlib 默认的的机制来加载 module

    #8 提到的避免 import 可能是添加一个 meta hook 吧,加载需要以后清除的 module 时,通过读取代码文件后手动 exec 来加载
    ChrisFreeMan
        23
    ChrisFreeMan  
       2022-03-02 00:42:04 +08:00
    这种规模的内存占用,不知道这辈子能不能遇上一次,我就围观好了
    lolizeppelin
        24
    lolizeppelin  
       2022-03-02 09:27:18 +08:00
    围观 好奇怎么能让代码这么大....

    代码里塞数据了?
    ruanimal
        25
    ruanimal  
       2022-03-02 10:15:42 +08:00
    @lolizeppelin 估计是模型文件之类的
    iyaozhen
        26
    iyaozhen  
    OP
       2022-03-02 16:24:55 +08:00
    @24owls 嗯嗯 我都试试
    iyaozhen
        27
    iyaozhen  
    OP
       2022-03-02 16:25:30 +08:00
    @ChrisFreeMan 这不是因为使用问题嘛 正常哪需要这么多
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   989 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 20:14 · PVG 04:14 · LAX 12:14 · JFK 15:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.