以最快的方式将大量数据写入文件

25

我正在尝试创建随机的实数、整数、字母数字和字母字符串,然后将其写入文件,直到文件大小达到10MB

代码如下。

import string
import random
import time
import sys


class Generator():
    def __init__(self):
        self.generate_alphabetical_strings()
        self.generate_integers()
        self.generate_alphanumeric()
        self.generate_real_numbers()

    def generate_alphabetical_strings(self):
        return ''.join(random.choice(string.ascii_lowercase) for i in range(12))

    def generate_integers(self):
        return ''.join(random.choice(string.digits) for i in range(12))

    def generate_alphanumeric(self):
        return ''.join(random.choice(self.generate_alphabetical_strings() +
                                     self.generate_integers()) for i in range(12))

    def _insert_dot(self, string, index):
        return string[:index].__add__('.').__add__(string[index:])


    def generate_real_numbers(self):
        rand_int_string = ''.join(random.choice(self.generate_integers()) for i in range(12))
        return self._insert_dot(rand_int_string, random.randint(0, 11))


from time import process_time
import os

a = Generator()

t = process_time()
inp = open("test.txt", "w")
lt = 10 * 1000 * 1000
count = 0
while count <= lt:
    inp.write(a.generate_alphanumeric())
    count += 39
inp.close()

elapsed_time = process_time() - t
print(elapsed_time)

完成此操作需要约225.953125秒。如何提高程序的速度?请提供一些代码洞见。


你的程序中花费了多少时间? - dm03514
1
@MartijnPieters 我在Java中尝试了相同的代码,只用了约0.93秒。 - ajknzhol
Java程序写入了磁盘。在进程完成后,我手动检查了文件的大小。 - ajknzhol
3个回答

49

观察到“缓慢”现象的两个主要原因:

  • 您的while循环速度较慢,它大约有一百万次迭代。
  • 您没有正确使用I/O缓冲。不要进行太多的系统调用。目前,您正在调用write()大约一百万次。

先在Python数据结构中创建数据,然后仅调用write() 一次

这样会更快:

t0 = time.time()
open("bla.txt", "wb").write(''.join(random.choice(string.ascii_lowercase) for i in xrange(10**7)))
d = time.time() - t0
print "duration: %.2f s." % d

输出: 持续时间:7.30秒。

现在程序大部分时间都花在生成数据上,即在random模块。你可以通过将random.choice(string.ascii_lowercase)替换为例如"a"来轻松查看。然后,在我的计算机上,测量时间下降到不到一秒。

如果您想更接近地了解写入磁盘时计算机的速度有多快,请使用Python中最快的 (?) 方法生成较大的数据,然后将其写入磁盘:

>>> t0=time.time(); chunk="a"*10**7; open("bla.txt", "wb").write(chunk); d=time.time()-t0; print "duration: %.2f s." % d
duration: 0.02 s.

3
你的问题是关于“适当利用IO缓冲”的含义。 - ajknzhol
24
您正在将数据写入磁盘。写入磁盘是一个复杂的物理和逻辑过程,涉及大量机械和控制。向磁盘发送“这里有10 MB的数据,请写入!”比告诉它“这里有1个字节的数据,请写入!”要快得多。因此,操作系统有一种机制,在实际保存到磁盘之前,“收集”进程想要写入磁盘的数据。但是,如果您明确告诉操作系统要写入小部分数据,那么它会立即执行。您正在这样做,这很慢。请查看我的编辑。 - Dr. Jan-Philip Gehrcke
@Jan-PhilipGehrcke:有没有一种方法可以创建一个缓冲文件写入器? - Aaron Digulla
4
如果在调用Python的open()时没有指定buffering参数,则通常会应用(小型)缓冲区,根据“系统默认值”。这个缓冲区的大小没有文档记录。对于某些版本的glibc,有人确定其大小为8 kB:https://dev59.com/questions/emMl5IYBdhLWcg3wsIyA#18194856。对于某些应用程序,增加`buffering`参数的缓冲区大小是有意义的。没有一般性的说明,但基准测试可以帮助你做出决定。有时候通过https://docs.python.org/2/library/stringio.html 显式地先将数据收集到内存中也是有意义的。 - Dr. Jan-Philip Gehrcke
同意这个答案中的观点。这里需要注意的一件重要的事情是buffering参数。对于较小的数据集(数百或数千项,总数据量在KB级别),无论哪种方式性能都没有显著差异。在我的分析中,每次迭代调用写入所花费的时间与单次写入调用所花费的时间相同(计算到毫秒精度)。我将缓冲设置为-1(这是默认的操作系统块大小)。 - Rishi
写入的内容是否实际写入磁盘?这样我们只需要执行一次吗? - undefined

2

您实际上创建了数十亿个对象,随后将它们快速丢弃。在这种情况下,最好直接将字符串写入文件,而不是使用''.join()进行拼接。


1
主函数下的 while 循环调用 generate_alphanumeric,它从由十二个 ASCII 字母和十二个数字组成的随机字符串中选择多个字符。这基本上相当于随机选择一个字母或一个数字重复十二次。这就是您的主要瓶颈。此版本将使您的代码快一个数量级:
def generate_alphanumeric(self):
    res = ''
    for i in range(12):
        if random.randrange(2):
            res += random.choice(string.ascii_lowercase)
        else:
            res += random.choice(string.digits)
    return res

我相信它可以得到改进。建议您试用一下您的性能分析器。


原始运行时间(在我的机器上)为0m28.587s。我的版本只需要0m2.266s。你还会做出哪个改变对性能影响更大呢? - debiatan
移除 while 循环,仅调用一次 write() - Dr. Jan-Philip Gehrcke
大部分时间已经浪费在不必要的随机生成上了。如果我削减了92%的原始运行时间,那这不就是瓶颈吗?一旦解决了这个问题,我相信你的建议会很有用。 - debiatan
好的,我们同意这一点:他的数据生成非常低效,而他的I/O代码也非常低效。哪一个是主要瓶颈取决于系统(在我配备好的SSD和CPU的系统上,它是I/O)。 - Dr. Jan-Philip Gehrcke
抱歉,我刚刚发现我一直在考虑他的generate_alphabetical_strings()方法,这并不是很糟糕(请参见下面我的答案)。实际上,当他使用generate_alphanumeric()时,这才是他的主要瓶颈。 - Dr. Jan-Philip Gehrcke
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接