保持文件句柄打开的缺点?
Downsides of keeping file handles open?
我正在解析一些 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 个左右,这与我在上述 post.
中读到的限制相去甚远
您已经提到的明显缺点是,打开所有文件句柄需要大量内存,当然这取决于文件的数量。这是您必须自己进行的计算。并且不要忘记写锁。
除此之外并没有太大的问题,但如果有一些预防措施会很好:
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():
你不 没有说进程最终会打开多少文件。如果它不是太多而造成问题,那么这可能是一个好方法。我怀疑如果不在您的数据和执行环境中尝试,您是否真的知道。
根据我的经验,open()
相对较慢,因此避免不必要的调用绝对值得考虑——您还可以避免设置所有关联的缓冲区,填充它们,每次关闭文件时刷新它们和垃圾收集。既然你问了,文件指针确实带有大缓冲区。在 OS X 上,默认缓冲区大小为 8192 字节 (8KB),对象有额外开销,与所有 Python 对象一样。因此,如果您有成百上千个文件和很少的 RAM,它会累加起来。您可以指定更少的缓冲或根本没有缓冲,但这可能会破坏通过避免重复打开而获得的任何效率。
编辑: 对于仅 35 个不同的文件(或任何两位数),您无需担心:35 个输出缓冲区将需要 space (实际缓冲每个缓冲区 8 KB)甚至不会是内存占用的最大部分。因此,请继续按照您建议的方式进行操作。与每个 xml 节点打开和关闭文件相比,您将看到显着的速度提升。
PS。默认缓冲区大小由 io.DEFAULT_BUFFER_SIZE
.
给出
如果您正在按照您可能考虑编写的方式生成数千个文件
他们到一个目录结构让他们分别存储在不同的
目录以便以后更容易访问。例如:a/a/aanode.txt、a/c/acnode.txt 等
如果 XML 包含连续的节点,您可以同时写入
条件为真。您只会在另一个文件的另一个节点出现时关闭。
您从中获得的收益在很大程度上取决于 XML 文件的结构。
作为一个好的规则,请尽快关闭文件。
请注意,您的操作系统也有限制 - 您只能打开一定数量的文件。因此,您可能很快就会达到此限制,并且您将开始收到 "Failed to open file" 个异常。
内存和文件句柄泄漏是明显的问题(如果由于某种原因无法关闭文件)。
我正在解析一些 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 个左右,这与我在上述 post.
中读到的限制相去甚远您已经提到的明显缺点是,打开所有文件句柄需要大量内存,当然这取决于文件的数量。这是您必须自己进行的计算。并且不要忘记写锁。
除此之外并没有太大的问题,但如果有一些预防措施会很好:
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():
你不 没有说进程最终会打开多少文件。如果它不是太多而造成问题,那么这可能是一个好方法。我怀疑如果不在您的数据和执行环境中尝试,您是否真的知道。
根据我的经验,open()
相对较慢,因此避免不必要的调用绝对值得考虑——您还可以避免设置所有关联的缓冲区,填充它们,每次关闭文件时刷新它们和垃圾收集。既然你问了,文件指针确实带有大缓冲区。在 OS X 上,默认缓冲区大小为 8192 字节 (8KB),对象有额外开销,与所有 Python 对象一样。因此,如果您有成百上千个文件和很少的 RAM,它会累加起来。您可以指定更少的缓冲或根本没有缓冲,但这可能会破坏通过避免重复打开而获得的任何效率。
编辑: 对于仅 35 个不同的文件(或任何两位数),您无需担心:35 个输出缓冲区将需要 space (实际缓冲每个缓冲区 8 KB)甚至不会是内存占用的最大部分。因此,请继续按照您建议的方式进行操作。与每个 xml 节点打开和关闭文件相比,您将看到显着的速度提升。
PS。默认缓冲区大小由 io.DEFAULT_BUFFER_SIZE
.
如果您正在按照您可能考虑编写的方式生成数千个文件 他们到一个目录结构让他们分别存储在不同的 目录以便以后更容易访问。例如:a/a/aanode.txt、a/c/acnode.txt 等
如果 XML 包含连续的节点,您可以同时写入 条件为真。您只会在另一个文件的另一个节点出现时关闭。 您从中获得的收益在很大程度上取决于 XML 文件的结构。
作为一个好的规则,请尽快关闭文件。
请注意,您的操作系统也有限制 - 您只能打开一定数量的文件。因此,您可能很快就会达到此限制,并且您将开始收到 "Failed to open file" 个异常。
内存和文件句柄泄漏是明显的问题(如果由于某种原因无法关闭文件)。