我需要生成一个非常大的文本文件。每行都有一个简单的格式:
Seq_num<SPACE>num_val
12343234 759
假设我要生成一个包含 1 亿行的文件。 我尝试了两种方法,令人惊讶的是它们给出了截然不同的时间性能。
For 循环超过 100 米。在每个循环中,我制作短字符串
seq_num<SPACE>num_val
,然后我将其写入文件。 这种方法需要很多时间。## APPROACH 1 for seq_id in seq_ids: num_val=rand() line=seq_id+' '+num_val data_file.write(line)
For 循环超过 100 米。在每个循环中,我制作短字符串
seq_num<SPACE>num_val
,然后我将其附加到列表中。 当循环结束时,我遍历列表项并将每个项目写入一个文件。 这种方法花费的时间少得多。## APPROACH 2 data_lines=list() for seq_id in seq_ids: num_val=rand() l=seq_id+' '+num_val data_lines.append(l) for line in data_lines: data_file.write(line)
注意:
- 方法 2 有 2 个循环而不是 1 个循环。
- 对于方法 1 和方法 2,我都在循环中写入文件。因此这一步必须对两者相同。
所以方法 1 必须花费更少的时间。有什么提示我遗漏了什么吗?
最佳答案
很多和远少于在技术上是非常模糊的术语:)基本上如果你不能衡量它,你就不能改进它。
为简单起见,我们有一个简单的基准测试,loop1.py
:
import random
from datetime import datetime
start = datetime.now()
data_file = open('file.txt', 'w')
for seq_id in range(0, 1000000):
num_val=random.random()
line="%i %f\n" % (seq_id, num_val)
data_file.write(line)
end = datetime.now()
print("elapsed time %s" % (end - start))
loop2.py
带有 2 个 for 循环:
import random
from datetime import datetime
start = datetime.now()
data_file = open('file.txt', 'w')
data_lines=list()
for seq_id in range(0, 1000000):
num_val=random.random()
line="%i %f\n" % (seq_id, num_val)
data_lines.append(line)
for line in data_lines:
data_file.write(line)
end = datetime.now()
print("elapsed time %s" % (end - start))
当我在我的计算机(使用 SSD 驱动器)上运行这两个脚本时,我得到类似的东西:
$ python3 loop1.py
elapsed time 0:00:00.684282
$ python3 loop2.py
elapsed time 0:00:00.766182
每次测量可能略有不同,但直觉表明,第二次测量稍慢。
如果我们想优化写入时间,我们需要检查the manual how Python implements writing into files .对于文本文件,open()
函数应该使用 BufferedWriter
.open
函数接受第三个参数,即缓冲区大小。这是有趣的部分:
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select line buffering (only usable in text mode), and an integer > 1 to indicate the size in bytes of a fixed-size chunk buffer. When no buffering argument is given, the default buffering policy works as follows:
Binary files are buffered in fixed-size chunks; the size of the buffer is chosen using a heuristic trying to determine the underlying device’s “block size” and falling back on io.DEFAULT_BUFFER_SIZE. On many systems, the buffer will typically be 4096 or 8192 bytes long.
因此,我们可以修改 loop1.py
并使用行缓冲:
data_file = open('file.txt', 'w', 1)
事实证明这很慢:
$ python3 loop3.py
elapsed time 0:00:02.470757
为了优化写入时间,我们可以根据需要调整缓冲区大小。首先,我们检查以字节为单位的行大小:len(line.encode('utf-8'))
,这给了我 11
字节。
将缓冲区大小更新为我们预期的字节大小后:
data_file = open('file.txt', 'w', 11)
我的写入速度非常快:
elapsed time 0:00:00.669622
根据您提供的详细信息,很难估计发生了什么。也许用于估计 block 大小的启发式方法在您的计算机上效果不佳。不管怎样,如果你写的是固定行长,优化缓冲区大小很容易。您可以利用 flush()
进一步优化文件写入.
结论:通常为了更快地写入文件,您应该尝试写入与文件系统上的 block 大小相对应的大量数据 - 这正是 Python 方法 open('file.txt', 'w')
正在尝试做。在大多数情况下,您使用默认设置是安全的,微基准测试中的差异微不足道。
您正在分配大量需要由 GC 收集的字符串对象。正如 @kevmo314 所建议的,为了进行公平比较,您应该为 loop1.py
禁用 GC:
gc.disable()
因为 GC 可能会在遍历循环时尝试删除字符串对象(您没有保留任何引用)。 seconds 方法保留对所有字符串对象的引用,GC 在最后收集它们。
关于python - 在 Python 中生成非常大的文本文件的时间性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49266939/