当内存中有巨大(33GB)的数据结构(交换中没有任何数据)时,Python将持续运行10分钟以上(在程序中的最后一条语句



我需要解析一个巨大的gz文件(大约10GB压缩,大约100GB未压缩(。该代码在内存中创建数据结构("data_struct"(。我在一台带有Intel(R) Xeon(R) CPU E5-2667 v4 @ 3.20GHz的机器上运行,该机器有16个CPU和大量RAM(即200+GB(,运行CentOS-6.9。我在Python3.6.3(CPython(中使用一个Class实现了这些东西,如下所示:

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=======================

我的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的调用来创建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
...
...

Python在最后一个print()语句后的10分钟到12分钟内一直运行。一个观察结果是,如果我在__del__()方法中有del self.data_struct语句,那么它只需要2分钟。我已经做了多次这些实验,并且由于__del__()del self.data_struct的存在/不存在,运行时间减少/增加。

我的问题:

  1. 我的理解是Python使用munmap()进行清理工作,但与Python不同的是,其他语言(如Perl(会立即释放内存并退出程序。按照上面所示的方式执行,我做得对吗?有没有办法告诉Python避免这种munmap()
  2. 为什么如果__del__()中没有del self.data_struct语句,清理需要10分钟以上,而如果__del__()中有del self.data_struct语句,清理只需要2分钟
  3. 有没有办法加快清理工作(即munmap()(
  4. 有没有一种方法可以在没有清理工作的情况下立即退出程序

我们对解决这个问题的其他想法/建议表示感谢。

请尝试更新版本的Python(至少3.8(?这显示了CPython的对象解除定位器中最坏情况二次时间算法的温和(!(形式的几个迹象,该算法在这里被重写(请注意,链接到这里的问题反过来包含一个链接到一个旧的StackOverflow帖子的链接,其中包含更多详细信息(:

https://bugs.python.org/issue37029

一些光泽

如果我的猜测是正确的,那么内存量并不是特别重要——相反,它是由CPython的";小对象分配器";(obmalloc.c(;运气不好";按照它们的存储器被释放的顺序。

当第一次编写该代码时,RAM不够大,无法容纳数百万个Python对象,因此没有人注意到释放逻辑的一个特定部分可能会占用分配的"数量的二次方时间;竞技场";(细节并没有真正的帮助,但"arenas"是系统mmap()munmap()调用的粒度——256个KiB块(。

并不是那些映射调用消耗了大量的时间,任何使用操作系统内存映射功能的语言的任何像样的实现最终都会调用munmap()足够的次数,以释放其mmap()调用所消耗的操作系统资源。

所以这只是转移注意力。munmap()被调用了很多次,只是因为您分配了许多对象,这需要多次mmap()调用。

没有任何清晰或简单的方法来准确解释问题何时出现。参见";运气不好";以上;-(CPython 3.8的相关代码被重写为最坏情况下的线性时间,这为触发问题报告的特定程序提供了约250的加速因子(见已给出的链接(。

正如一条注释所指出的,您可以通过调用os._exit()随时立即退出程序,但前导下划线是为了吓退您:";立即">表示";立即";。未进行任何形式的清理。例如,您的类中的__del__方法?跳过。__del__是作为解除分配的副作用运行的,但如果您实际上";立即释放存储器并退出程序";然后没有运行任何类型的析构函数,也没有向atexit模块注册的任何处理程序,等等。这就像程序死亡一样剧烈,例如,使用segfault。

最新更新