zoukankan      html  css  js  c++  java
  • Python3 os.walk()函数导致buffer/cache占用过高问题处理

    一、背景说明

    os.walk()应该是当前python中遍历目录最推荐的函数,之前用python写了一个用于收集系统用到的第三方组件的脚本,在测试时使用os.walk()遍历了部分目录,并通过了全网的测试。但在改成遍历根目录后,被业务反馈说脚本占用内存过高导致了内存告警。

    在直观感觉上,只遍历目录又不打开文件,应该只是相当于加载了一个目录树,不可能造成几十G内存的上涨。但一方面内存上涨时间和脚本的时间是一致的,另一方面在杀除脚本后内存出现了下降。所以基本可以确定内存上涨确实和该脚本是有关系的。

    经过反复的测试和观察,总结出以下两个现象:

    1. pythonos.walk和系统tree命令,只要文件一多,占用的buffer/cache就会明显上涨。
    2. find命令,如果/proc目录文件一多,占用的buffer/cache也会明显上涨。

    将该结论反馈给技术大佬,他分析之后给出这两个现象的更根本原因:

    1. pythonos.walk和系统tree命令在遍历目录时除了加载目录树还会加载文件的stat信息,所以文件一多就会占用很多buffer/cache
    2. find在遍历其他目录时只加载目录树不加域stat信息,所以不明显占用buffer/cache;但在遍历/proc时也会加载stat信息,所以/proc文件一多也会导致buffer/cache上涨。其实只是简单根据文件名的find不加载stat信息,如果根据日期等条件去find还是要加载stat信息。

     另外对于由于目录问题导致的buffer/cache上涨,可使用以下命令进行清理:

    sync; echo 2 > /proc/sys/vm/drop_caches

    参考:https://www.tecmint.com/clear-ram-memory-cache-buffer-and-swap-space-on-linux/

    二、问题处理

    所以buffer/cache涨不涨的两个因素已经很明显了:文件数量和加不加载文件stat信息。

    回到我们最初的目标收集所有第三方组件,这必然要求遍历整个磁盘,所以文件数量是不可限制的,所以只能想办法不加载文件的stat信息。不加载stat信息到现在看只好用普通的find命令,但这不是python原生的做法而且限制比较大。后来技术大佬看了find源码,仿照写了个不加stat信息的函数。

    import os
    import pdb
    from ctypes import CDLL, c_char_p, c_int, c_long, c_ushort, c_byte, c_char, Structure, POINTER
    from ctypes.util import find_library
    
    
    class c_dir(Structure):
        """Opaque type for directory entries, corresponds to struct DIR"""
        pass
    
    
    c_dir_p = POINTER(c_dir)
    
    
    class c_dirent(Structure):
        """Directory entry"""
        # FIXME not sure these are the exactly correct types!
        _fields_ = (
            ('d_ino', c_long),  # inode number
            ('d_off', c_long),  # offset to the next dirent
            ('d_reclen', c_ushort),  # length of this record
            ('d_type', c_byte),  # type of file; not supported by all file system types
            ('d_name', c_char * 4096)  # filename
        )
    
    
    c_dirent_p = POINTER(c_dirent)
    c_lib = CDLL(find_library("c"))
    opendir = c_lib.opendir
    opendir.argtypes = [c_char_p]
    opendir.restype = c_dir_p
    # FIXME Should probably use readdir_r here
    readdir = c_lib.readdir
    readdir.argtypes = [c_dir_p]
    readdir.restype = c_dirent_p
    closedir = c_lib.closedir
    closedir.argtypes = [c_dir_p]
    closedir.restype = c_int
    
    DT_FIFO = 1
    DT_CHR = 2
    DT_DIR = 4
    DT_BLK = 6
    DT_REG = 8
    DT_LNK = 10
    DT_SOCK = 12
    DT_WHT = 14
    
    
    def listdir(path):
        """
        A generator to return the names of files in the directory passed in
        """
        dir_p = opendir(path)
        try:
            while True:
                p = readdir(dir_p)
                if not p:
                    break
                name = p.contents.d_name
                if name not in (".", ".."):
                    yield name, p.contents.d_type
        finally:
            closedir(dir_p)
    
    
    def _traversal_path(name, parent, res_array, follow_link=False, ):
        if not os.path.exists(name):
            return
        cur = os.path.join(parent, name)
        if not os.path.isdir(name):
            res_array.append(cur)
        elif cur in dir_white_list:
            return
        else:
            for cn, ct in listdir(name):
                if ct & DT_DIR != DT_DIR:
                    res_array.append(os.path.join(cur, cn))
                elif not follow_link and (
                        ct & DT_LNK == DT_LNK
                ):
                    res_array.append(os.path.join(cur, cn))
                else:
                    os.chdir(name)
                    _traversal_path(cn, cur, res_array, follow_link)
                    os.chdir("..")
    
    
    def traversal_path(path, follow_link=False):
        # pdb.set_trace()
        files = []
        name = os.path.basename(path)
        parent = os.path.dirname(path)
        if name == "":
            name = parent
            parent = "."
        cur = os.curdir
        os.chdir(parent)
        _traversal_path(name, parent, files, follow_link)
        os.chdir(cur)
        return files
    
    
    # 白名单目录
    # 其实做了不加载stat信息处理,所以即便遍历/proc预期上也不会导致buffer/cache上涨
    # 但一般这些目录都是系统目录,尤其是/proc文件系统还比较复杂,所以我们直接略过省时省心
    dir_white_list = ["/proc", "/sys", "/dev", "/boot"]
    
    if __name__ == '__main__':
        for f in traversal_path("/"):
            print(f)
  • 相关阅读:
    Delphi XE4 FireMonkey 开发 IOS APP 发布到 AppStore 最后一步.
    Native iOS Control Delphi XE4
    Delphi XE4 iAD Framework 支持.
    using IOS API with Delphi XE4
    GoF23种设计模式之行为型模式之命令模式
    Android青翼蝠王之ContentProvider
    Android白眉鹰王之BroadcastReceiver
    Android倚天剑之Notification之亮剑IOS
    Android紫衫龙王之Activity
    GoF23种设计模式之行为型模式之访问者模式
  • 原文地址:https://www.cnblogs.com/lsdb/p/13163293.html
Copyright © 2011-2022 走看看