【问题标题】:Python - How to gzip a large text file without MemoryError?Python - 如何在没有 MemoryError 的情况下压缩大文本文件?
【发布时间】:2015-01-18 01:26:57
【问题描述】:

我使用以下简单的 Python 脚本在 EC2 m3.large 实例上压缩一个大文本文件(例如,10GB)。但是,我总是得到一个MemoryError

import gzip

with open('test_large.csv', 'rb') as f_in:
    with gzip.open('test_out.csv.gz', 'wb') as f_out:
        f_out.writelines(f_in)
        # or the following:
        # for line in f_in:
        #     f_out.write(line)

我得到的回溯是:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    f_out.writelines(f_in)
MemoryError

我已经阅读了一些关于这个问题的讨论,但仍然不太清楚如何处理这个问题。关于如何处理这个问题,有人能给我一个更容易理解的答案吗?

【问题讨论】:

  • 马克的解决方案的确切错误是什么?它不能在f_out.writelines,因为你使用write ...
  • 错误会是这样的:Traceback (most recent call last): File "test.py", line 8, in &lt;module&gt; for line in f_in: MemoryError

标签: python out-of-memory gzip large-files


【解决方案1】:

这里的问题与gzip无关,与从一个没有换行符的10GB文件中逐行读取有关:

作为附加说明,我用来测试 Python gzip 功能的文件是由 fallocate -l 10G bigfile_file 生成的。

这为您提供了一个完全由 0 字节组成的 10GB 稀疏文件。意味着没有换行字节。这意味着第一行是 10GB 长。这意味着读取第一行需要 10GB。 (或者甚至可能是 20 或 40GB,如果您使用的是 3.3 之前的 Python 并尝试将其读取为 Unicode。)

如果要复制二进制数据,请不要逐行复制。无论是普通文件、为您即时解压缩的GzipFilesocket.makefile() 或其他任何文件,您都会遇到同样的问题。

解决方案是逐块复制。或者只使用copyfileobj,它会自动为您完成。

import gzip
import shutil

with open('test_large.csv', 'rb') as f_in:
    with gzip.open('test_out.csv.gz', 'wb') as f_out:
        shutil.copyfileobj(f_in, f_out)

默认情况下,copyfileobj 使用优化的块大小,通常非常好,从不非常坏。在这种情况下,您实际上可能想要更小或更大的尺寸;很难预测哪个是先验的。* 因此,通过使用 timeit 和不同的 bufsize 参数(例如,从 1KB 到 8MB 的 4 的幂)到 copyfileobj 来测试它。但是默认的 16KB 可能就足够了,除非你做很多这样的事情。

* 如果缓冲区太大,您最终可能会交替使用长 I/O 块和长块处理。如果它太小,您最终可能需要多次读取才能填充单个 gzip 块。

【讨论】:

    【解决方案2】:

    这很奇怪。如果您尝试压缩不包含许多换行符的大型二进制文件,我预计会出现此错误,因为这样的文件可能包含对您的 RAM 来说太大的“行”,但它不应该发生在一行上 -结构化的 .csv 文件。

    但无论如何,逐行压缩文件效率不高。尽管操作系统会缓冲磁盘 I/O,但读取和写入更大的数据块(例如 64 kB)通常会快得多

    我在这台机器上有 2GB 的 RAM,我刚刚成功地使用下面的程序压缩了一个 2.8GB 的​​ tar 存档。

    #! /usr/bin/env python
    
    import gzip
    import sys
    
    blocksize = 1 << 16     #64kB
    
    def gzipfile(iname, oname, level):
        with open(iname, 'rb') as f_in:
            f_out = gzip.open(oname, 'wb', level)
            while True:
                block = f_in.read(blocksize)
                if block == '':
                    break
                f_out.write(block)
            f_out.close()
        return
    
    
    def main():
        if len(sys.argv) < 3:
            print "gzip compress in_file to out_file"
            print "Usage:\n%s in_file out_file [compression_level]" % sys.argv[0]
            exit(1)
    
        iname = sys.argv[1]
        oname = sys.argv[2]
        level = int(sys.argv[3]) if len(sys.argv) > 3 else 6
    
        gzipfile(iname, oname, level)
    
    
    if __name__ == '__main__':  
        main()
    

    我正在运行 Python 2.6.6,而 gzip.open() 不支持 with


    正如 Andrew Bay 在 cmets 中指出的那样,if block == '': 在 Python 3 中将无法正常工作,因为 block 包含字节,而不是字符串,并且空字节对象不与空文本字符串进行比较.我们可以检查块长度,或与b''(也适用于Python 2.6+)进行比较,但简单的方法是if not block:

    【讨论】:

    • 感谢您的回答,很清楚,效果很好:)
    • 补充说明,我用来测试 Python gzip 功能的文件是由fallocate -l 10G bigfile_file 生成的。 Python不能通过文件可迭代来gzip这么大的文件(这似乎是很久以前的一个错误?)。
    • @shihpeng:我不熟悉fallocate,所以这只是一个猜测,但也许Python 的gzip 不喜欢这样的文件,因为它们不包含任何实际数据。我无法测试它,因为我仍在这个系统上使用 ext3,它不支持fallocate。但是,我的程序使用truncate 创建的大文件可以正常工作,该文件会创建稀疏文件。
    • “尽管操作系统缓冲磁盘 I/O,但读取和写入更大的数据块(例如 64 kB)通常要快得多。” GzipFile 使用缓冲 I/O(Python 2.x 中的 C stdio 缓冲区或 3.x 中的 Python io 缓冲区)。它仅在尝试解压缩另一个 zlib 块并且该块超出缓冲区时才从磁盘读取。所以它已经在做你想做的所有事情了。唯一的区别是您使用的是更大的块大小;如果这确实有帮助,您可以手动打开文件并从中构造一个GzipFile,而不是使用gzip.open
    • 在 Python 3.x 中,不使用 if block == '',而是使用块的长度来确定块是否为空。这是因为字符串是 unicode 并且无法与块进行比较。
    【解决方案3】:

    即使在逐行读取文件时也会出现内存错误,这很奇怪。我想这是因为你的可用内存很少,而且行很长。然后你应该使用二进制读取:

    import gzip
    
    #adapt size value : small values will take more time, high value could cause memory errors
    size = 8096
    
    with open('test_large.csv', 'rb') as f_in:
        with gzip.open('test_out.csv.gz', 'wb') as f_out:
            while True:
                data = f_in.read(size)
                if data == '' : break
                f_out.write(data)
    

    【讨论】:

    • 是的,m3.large 只有 2 个 vcpu 和 7gb 内存,如果有其他进程或服务器在同一个实例上运行,这是非常有限的。
    • 这只会复制前 8KB。
    • 现在它将永远循环,在 EOF 之后永远写入空字符串。
    • @abarnert: 修复了......这次经过测试:-)
    • @SergeBallesta:你应该会看到我编写的一些 180 行的怪物,并花了几个小时调试才意识到我已经免费复制了 stdlib 附带的一些东西。 :)
    猜你喜欢
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多