【问题标题】:Downsides of keeping file handles open?保持文件句柄打开的缺点?
【发布时间】:2018-06-25 14:44:52
【问题描述】:

我正在解析一些 XML 并将数据写入不同的文件,具体取决于当前正在处理的 XML 元素。处理一个元素真的很快,写入数据也是。因此,文件需要经常打开和关闭。例如,给定一个巨大的file:

for _, node in lxml.etree.iterparse(file):
    with open(f"{node.tag}.txt", 'a') as fout:
        fout.write(node.attrib['someattr']+'\n'])

这可行,但相对而言,打开和关闭文件会花费很多时间。 (注意:这是一个玩具程序。实际上我写入文件的实际内容以及文件名是不同的。有关数据详细信息,请参见最后一段。)

另一种可能是:

fhs = {}
for _, node in lxml.etree.iterparse(file):
    if node.tag not in fhs:
        fhs[node.tag] = open(f"{node.tag}.txt", 'w')

    fhs[node.tag].write(node.attrib['someattr']+'\n'])

for _, fh in fhs.items(): fh.close()

这将使文件保持打开状态,直到 XML 解析完成。有一些查找开销,但与迭代打开和关闭文件相比,这应该是最小的。

我的问题是,这种方法的缺点是什么?我知道这会使其他进程无法访问打开的文件,并且您可能会遇到a limit of open files。但是,我对性能问题更感兴趣。保持所有文件句柄打开是否会产生某种内存问题或处理问题?在这种情况下可能会发生过多的文件缓冲?我不确定,所以这个问题。

输入的 XML 文件最大可达 70GB 左右。生成的文件数量限制在 35 个左右,这与我在上述帖子中读到的限制相去甚远。

【问题讨论】:

  • 你能定义:huge 并估计 # 个输出文件吗?
  • 从广义上讲,避免进行 OS 系统调用将加快您的代码的速度——换句话说,不打开和关闭文件会更快反复。这是否重要取决于您到底在做什么。每个打开的文件都需要一些内存,因此如果同时打开大量文件,这可能会成为一个问题(但这是值得怀疑的,IMO)。
  • @hootnot 我添加了一段提供更多数据信息的段落。
  • @martineau 请看我的编辑。
  • Bram:在这种情况下,我听上去不会担心通过同时打开/同时打开它们会占用太多内存。

标签: python python-3.x performance file-handling


【解决方案1】:

您已经提到的明显缺点是,需要大量内存来保持所有文件句柄处于打开状态,这当然取决于文件的数量。这是您必须自己进行的计算。并且不要忘记写锁。

否则的话并没有太大的问题,但如果采取一些预防措施会很好:

fhs = {}
try:
    for _, node in lxml.etree.iterparse(file):
        if node.tag not in fhs:
            fhs[node.tag] = open(f"{node.tag}.txt", 'w')

        fhs[node.tag].write(node.attrib['someattr']+'\n'])
finally:
    for fh in fhs.values(): fh.close()

注意: 当在 python 中循环一个 dict 时,你得到的项目实际上只是键。我建议做for key, item in d.items(): 或for item in d.values():

【讨论】:

  • 打开的文件是否保存在内存中?我会假设文件 handles 保存在内存中,而不是完整的文件本身。
  • 不,内容不会保存在内存中,除非您每次打开它们时都进行读取。
  • @martineau 正确,意思是说文件的数量,而不是内容。
  • OP 代码中的for fh in fhs: fh.close() 也是错误的,但方式不完全相同。我认为我的建议是简化并正确使用变量名是更好的方法。
  • 在 Python 3 中,values() 返回一个迭代器(就像在 Python 2 中删除的 intervalues())。由于我们在这里讨论的是进行大量操作系统系统调用,因此可能不值得尝试在这种详细程度进行优化。
【解决方案2】:

您不要没有说明该进程最终会打开多少文件。如果它不是太多会产生问题,那么这可能是一个好方法。我怀疑如果不使用数据和执行环境进行尝试,您是否真的能知道。

根据我的经验,open() 相对较慢,因此避免不必要的调用绝对值得考虑——您还避免设置所有关联的缓冲区、填充它们、每次关闭文件时刷新它们以及垃圾——收集。既然你问了,文件指针确实带有大缓冲区。在 OS X 上,默认缓冲区大小为 8192 字节 (8KB),并且对象有额外的开销,就像所有 Python 对象一样。因此,如果您有成百上千的文件和少量的 RAM,它可以加起来。您可以指定更少的缓冲或完全不缓冲,但这可能会破坏避免重复打开所获得的任何效率。

编辑:对于仅 35 个不同的文件(或任何两位数),您无需担心:35 个输出缓冲区将需要的空间(实际每个缓冲区 8 KB缓冲)甚至不会是你内存占用的最大部分。因此,请继续按照您的建议进行操作。与为每个 xml 节点打开和关闭文件相比,您会看到速度显着提高。

PS。默认缓冲区大小由io.DEFAULT_BUFFER_SIZE 给出。

【讨论】:

  • 我担心我的方法是因为句柄保持打开状态,所以不会在短时间内刷新任何内容(因为文件未关闭),如果是多个文件的情况,所有这些缓冲区组合起来可能会占用相当多的内存。还是 Python/操作系统如此聪明以至于它不会让缓冲区填满内存? (我在我的 OP 中添加了一些数据信息。)
  • 输出缓冲区有一个固定的、有限的长度——这就是缓冲区。在第一次填满之前,它们可能会占用更少的空间,但之后内存占用将保持不变:缓冲区填满,然后刷新。但是,在某些配置下,您的进程可能会在所有打开的文件句柄最后一次刷新之前终止(在一切正常执行之后)。因此,您应该在结束执行之前关闭所有打开的文件句柄,以强制刷新。
【解决方案3】:

作为一个好的规则,请尝试尽快关闭文件。

请注意,您的操作系统也有限制 - 您只能打开一定数量的文件。因此,您可能很快就会达到此限制,并且您将开始收到“无法打开文件”异常。

内存和文件句柄泄漏是明显的问题(如果您由于某种原因未能关闭文件)。

【讨论】:

  • 但是,当您尽快关闭文件并且必须再次打开它(并重复此过程> 100 万次)时,打开文件所需的时间会占用大量时间您的处理时间。
【解决方案4】:

如果您正在以您可能考虑编写的方式生成数千个文件 它们到一个目录结构中,以使它们分别存储在不同的 目录以便之后更容易访问。例如:a/a/aanode.txt、a/c/acnode.txt等

如果 XML 包含您可以编写的连续节点 条件为真。您仅在出现另一个文件的另一个节点时才关闭。 您从中获得什么很大程度上取决于您的 XML 文件的结构。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多