Python 为什么file.close()会减慢我的代码
我正在编写一个程序,生成指定数量的句子,每个句子都写入一个文件。我一直在尝试优化1000万句以上的案例代码 最近,为了提高写性能,我在open调用中将buffer参数指定为512MB,但是,我的代码实际上慢了3秒。罪魁祸首是名为“\u io.TextIOWrapper”对象的Python 为什么file.close()会减慢我的代码,python,performance,io,Python,Performance,Io,我正在编写一个程序,生成指定数量的句子,每个句子都写入一个文件。我一直在尝试优化1000万句以上的案例代码 最近,为了提高写性能,我在open调用中将buffer参数指定为512MB,但是,我的代码实际上慢了3秒。罪魁祸首是名为“\u io.TextIOWrapper”对象的{method'close}的东西。我认为这与file close方法有关,但这是我第一次比较概要文件输出 这就是我以前的写入速度有多慢: 10000000 49.057 0.000 49.057 0.0
{method'close}
的东西。我认为这与file close方法有关,但这是我第一次比较概要文件输出
这就是我以前的写入速度有多慢:
10000000 49.057 0.000 49.057 0.000 {method 'write' of '_io.TextIOWrapper' objects}
现在是这样的:
10000000 3.184 0.000 3.184 0.000 {method 'write' of '_io.TextIOWrapper' objects}
相当大的改进
这是我的老方法:
3 4.003 1.334 4.003 1.334 {method 'close' of '_io.TextIOWrapper' objects}
这是我的新的:
1 62.668 62.668 62.668 62.668 {method 'close' of '_io.TextIOWrapper' objects}
这是我的密码:
def sentence_maker(nouns, verbs, number_of_sentences, file_name):
writer = open(file_name, "w", 536870912)
for num in range(number_of_sentences):
string = (choice(nouns) + " " + choice(verbs) + " " + choice(nouns))
writer.write(string + "\n")
writer.close()
为什么close()
这么慢
注意:在程序的前面某个地方,我曾经有一些
close()
语句,因此在我以前的close()
示例中出现了ncalls=3
。我已经确定这些对性能没有明显的影响。写入磁盘的速度很慢,所以许多程序将写入存储到大块中,然后一次写入。这称为缓冲,Python会在您打开文件时自动执行。当您写入文件时,实际上是在写入内存中的“缓冲区”。当它填满时,Python将自动将其写入磁盘或在调用close()时将其写入磁盘。写入磁盘的速度很慢,因此许多程序会将写入存储到大块中,然后一次全部写入。这称为缓冲,Python会在您打开文件时自动执行。当您写入文件时,实际上是在写入内存中的“缓冲区”。当它填满时,Python将自动将其写入磁盘或调用close()。您明确选择使用一个巨大的缓冲区(536870912是刷新缓冲区之前缓冲的字节数,大约半GB内存)close
包含对缓冲区中剩余内容的隐式flush
,假设你写了很多东西,这意味着它需要全部写出来
您必须在某个时候为实际I/O付费;一个大的缓冲区使得write
变得便宜(因为它实际上不执行任何I/O),但是一个如此大的缓冲区只是延迟痛苦,而不是避免痛苦。我怀疑任何超过1MB的缓冲区大小实际上会节省有意义的工作(并且限制可能更低);如果经常执行系统调用,则执行系统调用的成本很高,但如果每次调用所做的工作(实际的物理I/O)超过这两个调用一个数量级或更多,则每MB一次调用和每512 MB一次调用之间的差异就没有意义
作为比较,使用缓冲区的原因是,与常规函数调用()相比,系统调用具有较高的开销。CPython在I/O(释放和恢复GIL)中涉及一些额外的系统调用,因此系统调用
write
与memcpy
的增量成本可能相差100-1000倍。但就现代CPU的开销而言,即使是2000个滴答声也在微秒范围内。但I/O本身要比这贵得多;写入10MB的数据可能需要十分之一秒左右的时间当I/O本身的成本以秒为单位时,通过使用较大的缓冲区大小在系统调用中节省几毫秒并不重要。而且如此大的缓冲区将开始引入缓存未命中(以及可能的页面错误),而较小的缓冲区可以避免这些情况。您明确选择使用较大的缓冲区(536870912是刷新缓冲区之前缓冲的字节数,大约半GB的内存)。close
包括对缓冲区中剩余内容的隐式flush
,假设您写了很多,这意味着需要将其全部写出来
在某个时候,你必须为实际的I/O付费;一个大的缓冲区会使write
变得便宜(因为它实际上不执行任何I/O),但是一个如此大的缓冲区只是推迟痛苦,而不是避免痛苦。我怀疑任何超过1MB的缓冲区都会实际节省有意义的工作(限制可能更低);如果经常执行系统调用,则执行系统调用的成本很高,但如果每次调用所做的工作(实际物理I/O)超过这两个调用一个数量级或更多,则每MB一次调用与每512 MB一次调用之间的差异就没有意义
为了进行比较,使用缓冲区的原因是,与常规函数调用()相比,系统调用具有较高的开销。CPython在I/O中涉及一些额外的系统调用(释放和恢复GIL)因此,系统调用
写入
的增量成本与memcpy
的增量成本可能相差100-1000倍。但就现代CPU的开销而言,即使2000个滴答声也在微秒范围内。但I/O本身要比这贵得多;写入10 MB数据可能需要十分之一秒左右的时间。节省了几mil当I/O本身的成本以秒为单位时,使用更大的缓冲区大小进行系统调用的时间不重要。而且如此大的缓冲区将开始引入缓存未命中(以及可能的页面错误)这将避免使用较小的缓冲区。请确保在发布Python代码时准确地复制缩进。缩进严重的Python代码是胡说八道。您是否尝试过使用带有打开(,'w')的作为fp:
样式进行测试?通常最好使用带有的而不是“正常”关闭文件。由于close
也会刷新文件缓冲区,请尝试使用显式writer.flush()
@Bakuriu()进行测量。是的……要使缓冲区溢出需要134行和大约一百万行。我想性能开销来自维护缓冲区所需的内存分配/管理(特别是正如切普纳所指出的——如果某些缓冲区最终被交换掉的话),以及