推荐学习书目
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 · Mar 1, 2022 · 5246 views
    This topic created in 1547 days ago, the information mentioned may be changed or developed.
    有个业务需要动态加载很多本地文件( 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
    Supplement 1  ·  Mar 2, 2022
    稍等解释下为什么这么大
    1. 动态加载的是 pb 生成的 xxx_pb2.py 文件,熟悉的同学都了解,pb 生成的代码冗余信息很多,很大,甚至 pb 源文件内容都包含在里面,我大概看了下生成的文件大的有 2.4M 小的几十 k 。这只是 py 文件的大小,再加载到内存里应该更大(没实际算过)
    而且 Python 加载机制不只是 sys.modules ,别的地方也有占用

    2. 服务运行模式选的不对,服务启动的时候开了 10 个 worker 进程,会导致占用大了 10 倍,其实开 1 个 worker 进程就行
    27 replies    2022-03-02 16:25:30 +08:00
    keepeye
        1
    keepeye  
       Mar 1, 2022
    帮顶,试试 importlib.invalidate_caches() 不知道是不是用来清缓存的
    iyaozhen
        2
    iyaozhen  
    OP
       Mar 1, 2022
    @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  
       Mar 1, 2022   ❤️ 1
    https://stackoverflow.com/questions/3105801/unload-a-module-in-python

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

    ------

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

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

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

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

    代码里塞数据了?
    ruanimal
        25
    ruanimal  
       Mar 2, 2022
    @lolizeppelin 估计是模型文件之类的
    iyaozhen
        26
    iyaozhen  
    OP
       Mar 2, 2022
    @24owls 嗯嗯 我都试试
    iyaozhen
        27
    iyaozhen  
    OP
       Mar 2, 2022
    @ChrisFreeMan 这不是因为使用问题嘛 正常哪需要这么多
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1413 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 58ms · UTC 17:02 · PVG 01:02 · LAX 10:02 · JFK 13:02
    ♥ Do have faith in what you're doing.