Shi*_*iva 5 mmap data-structures python-3.x
我需要解析一个巨大的 gz 文件(约 10GB 压缩,约 100GB 未压缩)。该代码在内存中创建数据结构 ('data_struct')。我在一台有Intel(R) Xeon(R) CPU E5-2667 v4 @ 3.20GHz16 个 CPU 和充足 RAM(即 200+ GB)的机器上运行,运行 CentOS-6.9。我已经使用 Python3.6.3 (CPython) 中的类实现了这些东西,如下所示:
class my_class():
def __init__(self):
cmd = f'gunzip huge-file.gz'
self.process = subprocess(cmd, stdout=subprocess.PIPE, shell=True)
self.data_struct = dict()
def populate_struct(self):
for line in process.stdout:
<populate the self.data_struct dictionary>
def __del__():
self.process.wait()
#del self.data_struct # presence/absence of this statement decreases/increases runtime respectively
#================End of my_class===================
def main():
my_object = my_class()
my_object.populate_struct()
print(f'~~~~ Finished populate_struct() ~~~~') # last statement in my program.
## Python keeps running at 100% past the previous statement for 10+mins
if __name__ == '__main__':
main()
#================End of Main=======================
Run Code Online (Sandbox Code Playgroud)
我data_struct在内存中的常驻内存消耗(仅 RAM,无交换)约为 33GB。我确实$ top找到了 Python 进程的 PID 并使用$ strace -p <PID> -o <out_file>(以查看 Python 正在做什么)跟踪 Python 进程。当它正在执行时populate_struct(),我可以在 strace 的 out_file 中看到 Python 正在使用类似mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b0684160000 create 的调用data_struct。当 Python 运行超过最后一条print()语句时,我发现 Python 只发出munmap()如下所示的操作:
munmap(0x2b3c75375000, 41947136) = 0
munmap(0x2b3c73374000, 33558528) = 0
munmap(0x2b4015d2a000, 262144) = 0
munmap(0x2b4015cea000, 262144) = 0
munmap(0x2b4015caa000, 262144) = 0
munmap(0x2b4015c6a000, 262144) = 0
munmap(0x2b4015c2a000, 262144) = 0
munmap(0x2b4015bea000, 262144) = 0
munmap(0x2b4015baa000, 262144) = 0
...
...
Run Code Online (Sandbox Code Playgroud)
Python 在最后一条print()语句后 10 分钟到 12 分钟之间继续运行。一个观察是,如果我del self.data_struct在__del__()方法中有语句,那么它只需要 2 分钟。我已经多次进行了这些实验,并且运行时间因del self.data_structin的存在/不存在而减少/增加__del__()。
我的问题:
munmap(),但与 Python 不同的是,Perl 等其他语言会立即释放内存并退出程序。通过实施如上所示,我做得对吗?有没有办法告诉 Python 避免这种情况munmap()?del self.data_struct在声明中__del__(),如果有只需要2分钟,以清除del self.data_struct声明__del__()?munmap()?感谢有关解决此问题的其他想法/建议。
请尝试更新版本的 Python(至少 3.8)?这显示了 CPython 对象释放器中最坏情况二次时间算法的温和(!)形式的几个迹象,该算法在此处重写(请注意,链接到此处的问题又包含指向较旧的 StackOverflow 帖子的链接,其中包含更多细节):
https://bugs.python.org/issue37029
如果我的猜测是正确的,那么内存量并不是特别重要——而是由 CPython 的“小对象分配器”( obmalloc.c) 管理的不同 Python 对象的绝对数量,以及按内存顺序排列的“运气不好”发行了。
当第一次编写该代码时,RAM 不够大,无法容纳数百万个 Python 对象,因此没有人注意到释放逻辑的一个特定部分可能会花费分配“竞技场”数量的二次方的时间(详细信息并没有多大帮助,但“arena”是系统mmap()和munmap()调用的粒度- 256 KiB 块)。
并不是那些映射调用消耗了大量的时间,并且使用操作系统内存映射工具的任何语言的任何体面的实现最终都会调用munmap()足够的时间来释放其调用所消耗的操作系统资源mmap()。
所以这是一个转移注意力的事情。munmap()被调用多次只是因为您分配了许多对象,这需要多次mmap()调用。
没有任何清晰或简单的方法可以准确解释问题何时出现。请参阅上面的“运气不好”;-) 相关代码已针对 CPython 3.8 重写为最坏情况线性时间,这为触发问题报告的特定程序提供了约 250 倍的加速(请参阅已给出的链接) 。
正如评论指出的,您可以随时通过调用立即退出程序os._exit(),但前导下划线是为了吓跑您:“立即”意味着“立即”。不执行任何类型的清理。例如,__del__你的类中的方法?跳过。__del__是作为释放的副作用运行的,但如果您实际上“立即释放内存并退出程序”,则不会运行任何类型的析构函数,也不会运行任何向atexit模块注册的处理程序等。这就像程序死亡一样剧烈,例如,有段错误。