逐行处理非常大(> 20GB)的文本文件

时间:2013-05-21 11:56:46

标签: python line

我需要处理一些非常大的文本文件,最大的是大约60GB。

每行在七个字段中有54个字符,我想从前三个字段中删除最后三个字符 - 这应该会将文件大小减少大约20%。

我是Python的新手,并且有一个代码可以按照每小时大约3.4 GB的速度完成我想做的事情,但这是一个值得的练习,我真的需要至少达到10 GB /小时 - 有没有加快这个速度的方法?这段代码并没有接近挑战我的处理器,所以我做了一个没有受过教育的猜测,它受到内部硬盘读写速度的限制?

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:
        x = l.split(' ')[0]
        y = l.split(' ')[1]
        z = l.split(' ')[2]
        w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
        l = r.readline()
    r.close()
    w.close()

任何帮助都会非常感激。我在Windows 7上使用IDLE Python GUI并拥有16GB的内存 - 也许一个不同的操作系统会更有效率。

编辑:以下是要处理的文件的摘录。

70700.642014 31207.277115 -0.054123 -1585 255 255 255
70512.301468 31227.990799 -0.255600 -1655 155 158 158
70515.727097 31223.828659 -0.066727 -1734 191 187 180
70566.756699 31217.065598 -0.205673 -1727 254 255 255
70566.695938 31218.030807 -0.047928 -1689 249 251 249
70536.117874 31227.837662 -0.033096 -1548 251 252 252
70536.773270 31212.970322 -0.115891 -1434 155 158 163
70533.530777 31215.270828 -0.154770 -1550 148 152 156
70533.555923 31215.341599 -0.138809 -1480 150 154 158

11 个答案:

答案 0 :(得分:23)

编写像这样的代码更加惯用

def ProcessLargeTextFile():
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

这里的主要保存是只执行split一次,但如果CPU没有征税,这可能会产生很小的差异

可以帮助一次保存几千行,并一次性写入它们以减少硬盘的颠簸。一百万行 54MB的RAM!

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)
由@Janne建议,另一种生成线的方法

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z, rest = line.split(' ', 3)
            bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest)))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)

答案 1 :(得分:12)

测量!你有一些有用的提示,如何改进你的python代码,我同意他们。但你应该首先弄清楚,你真正的问题是什么。找到瓶颈的第一步是:

  • 从代码中删除所有处理。只需读取和写入数据并测量速度。如果只是读取和写入文件太慢,那就不是代码问题。
  • 如果只是读写速度很慢,请尝试使用多个磁盘。你正在同时阅读和写作。在同一张光盘上?如果是,请尝试使用不同的光盘,然后重试。
  • 某些异步io库(Twisted?)也可能有所帮助。

如果您发现了确切的问题,请再次询问该问题的优化。

答案 2 :(得分:7)

由于您似乎不受CPU限制,而是受I / O限制,您是否尝试过对open的第三个参数进行一些修改?

实际上,第三个参数可用于给出用于文件操作的缓冲区大小!

从文件中读取时,简单地写open( "filepath", "r", 16777216 )将使用16 MB缓冲区。它必须有所帮助。

对输出文件使用相同的文件,并使用相同的文件进行测量/比较。

注意:这是其他人建议的同类优化,但您可以在此免费获取,无需更改代码,无需自行缓冲。

答案 3 :(得分:7)

我将添加这个答案来解释为什么缓冲是有意义的,并提供一个更多的解决方案

你的表现令人惊叹。这篇文章Is it possible to speed-up python IO?表明,读取10 gb应该在3分钟左右。顺序写入速度相同。所以你错过了30倍,你的表现目标仍然比应该可能的慢10倍。

几乎可以肯定,这种差异在于磁盘正在进行的磁头搜索次数。头部搜索需要几毫秒。单个搜索对应于几兆字节的顺序读写。非常昂贵。在同一磁盘上复制操作需要在输入和输出之间进行搜索。如上所述,减少搜索的一种方法是以这样的方式缓冲,即在写入磁盘之前读取许多兆字节,反之亦然。如果你可以说服python io系统做到这一点,那太好了。否则,您可以将行读取并处理成字符串数组,然后在准备好50 MB的输出后写入。该大小意味着搜索将导致关于数据传输本身的<10%的性能损失。

另一种消除输入和输出文件之间寻求的非常简单的方法是使用具有两个物理磁盘的机器,并为每个物理磁盘完全分离io通道。从一个输入。输出到其他。如果您正在进行大量的大文件转换,那么拥有具有此功能的计算机是件好事。

答案 4 :(得分:4)

ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:

正如已经建议的那样,您可能希望使用for循环来使其更加优化。

    x = l.split(' ')[0]
    y = l.split(' ')[1]
    z = l.split(' ')[2]

您在此处执行拆分操作3次,具体取决于每行的大小,这将对性能产生不利影响。您应该拆分一次并将x,y,z分配给返回的数组中的条目。

    w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

您正在阅读的每一行,您正在立即写入该文件,这非常I / O密集。您应该考虑将输出缓冲到内存并定期推送到磁盘。像这样:

BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    buf = ""
    bufLines = 0
    for lineIn in r:

        x, y, z = lineIn.split(' ')[:3]
        lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])
        bufLines+=1

        if bufLines >= BUFFER_SIZE:
            # Flush buffer to disk
            w.write(buf)
            buf = ""
            bufLines=1

        buf += lineOut + "\n"

    # Flush remaining buffer to disk
    w.write(buf)
    buf.close()
    r.close()
    w.close()

您可以调整BUFFER_SIZE以确定内存使用和速度之间的最佳平衡。

答案 5 :(得分:3)

您的代码非常不恰当,并且可以进行比所需更多的函数调用。更简单的版本是:

ProcessLargeTextFile():
    with open("filepath") as r, open("output") as w:
        for line in r:
            fields = line.split(' ')
            fields[0:2] = [fields[0][:-3], 
                           fields[1][:-3],
                           fields[2][:-3]]
            w.write(' '.join(fields))

我不知道现在的文件系统比Windows慢。既然看起来您正在使用这些庞大的数据文件作为数据库,您是否考虑过使用真正的数据库?

最后,如果您只想减小文件大小,是否考虑过压缩/压缩文件?

答案 6 :(得分:2)

那些看似非常大的文件......它们为什么这么大?你在做什么处理?为什么不使用带有一些map reduce调用的数据库(如果适用)或简单的数据操作?数据库的要点是抽象处理和管理大量数据,这些数据不能全部适合内存。

你可以开始使用sqlite3这个只使用平面文件作为数据库的想法。如果你觉得这个想法很有用,那就升级到像postgresql那样更强大,更通用的东西。

创建数据库

 conn = sqlite3.connect('pts.db')
 c = conn.cursor()

创建表

c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')

然后使用上述算法之一通过调用

插入数据库中的所有行和点
c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")

现在你如何使用它取决于你想做什么。例如,通过执行查询来处理文件中的所有点

c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")

使用

从此查询中一次获取n
c.fetchmany(size=n)

我确定某处的sql语句有更好的包装,但你明白了。

答案 7 :(得分:2)

由于您只提到节省空间作为一种好处,是否有一些原因您不能只存储gzip压缩的文件?这应该节省70%以上的数据。或者如果随机访问仍然很重要,可以考虑让NTFS压缩文件。在其中任何一个之后,您将在I / O时间上获得更大的节省。

更重要的是,您的数据在哪里只能达到3.4GB /小时?这与USBv1速度有关。

答案 8 :(得分:1)

您可以先尝试保存拆分结果,而不是每次需要字段时都执行此操作。可能会加速。

你也可以尝试不在gui中运行它。在cmd中运行它。

答案 9 :(得分:1)

使用for l in r:读取文件以从缓冲中受益。

答案 10 :(得分:1)

此处提供了用于加载任何大小的文本文件而不会引起内存问题的代码。它支持千兆字节大小的文件。它可以在任何类型的机器上顺利运行,您只需要根据系统RAM配置CHUNK_SIZE。 CHUNK_SIZE越多,一次读取的数据越多

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

下载文件data_loading_utils.py并将其导入您的代码中

用法

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(line, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=process_lines)

process_lines方法是回调函数。所有行都将调用它,参数行一次代表文件的一行。

您可以根据计算机的硬件配置来配置变量 CHUNK_SIZE