Python和zlib:非常慢的解压缩连接流

时间:2013-05-12 10:52:25

标签: python stream zlib compression

我已经提供了一个压缩文件,其中包含多个单独的压缩XML流。压缩文件为833 mb。

如果我尝试将其解压缩为单个对象,我只会得到第一个流(大约19 kb)。

我修改了以下代码作为older question的答案,解压缩每个流并将其写入文件:

import zlib

outfile = open('output.xml', 'w')

def zipstreams(filename):
    """Return all zip streams and their positions in file."""
    with open(filename, 'rb') as fh:
        data = fh.read()
    i = 0
    print "got it"
    while i < len(data):
        try:
            zo = zlib.decompressobj()
            dat =zo.decompress(data[i:])
            outfile.write(dat)
            zo.flush()
            i += len(data[i:]) - len(zo.unused_data)
        except zlib.error:
            i += 1
    outfile.close()

zipstreams('payload')
infile.close()

此代码运行并生成所需的结果(将所有XML数据解压缩到单个文件中)。问题是工作需要几天时间!

即使压缩文件中有数万个流,但看起来这应该是一个更快的过程。大约8天减压833mb(估计3gb原料)表明我做错了。

还有另一种方法可以更有效地做到这一点,还是读取 - 解压缩 - 写入的结果速度慢 - 重复我遇到的瓶颈?

感谢您提出的任何建议或建议!

3 个答案:

答案 0 :(得分:4)

如果没有更多关于你实际处理的文件格式的具体知识,很难说很多,但很明显你的算法对子串的处理是二次的 - 当你有成千上万的时候,这不是一件好事。他们。所以,让我们看看我们所知道的:

您说供应商声明他们是

  

使用标准的zlib压缩库。这些压缩程序与构建gzip实用程序的压缩程序相同。

由此我们可以得出结论,组件流是原始zlib格式封装在gzip包装器(或PKZIP存档,或其他)中。有关ZLIB格式的权威文档位于:http://tools.ietf.org/html/rfc1950

因此,我们假设您的文件与您描述的完全一样:一个32字节的标头,然后是连接在一起的原始ZLIB流,其间没有任何其他内容。编辑:< / strong>毕竟不是这样的。

Python的zlib documentation提供了一个Decompress类,它实际上非常适合于浏览文件。它包含一个属性unused_datadocumentation明确指出:

  

确定压缩数据字符串结束位置的唯一方法是实际解压缩。这意味着当压缩数据包含在较大文件的一部分中时,您只能通过读取数据并将其后跟一些非空字符串添加到解压缩对象的decompress()方法中来找到它的结尾,直到unused_data属性不再为止空字符串。

所以,这就是你可以做的:编写一个循环,通过data读取,例如,一次一个块(甚至不需要将整个800MB文件读入内存)。将每个块推送到Decompress对象,然后检查unused_data属性。当它变为非空时,你就有了一个完整的对象。将其写入磁盘,创建一个新的解压缩对象并使用最后一个unused_data初始化iw。这可能有效(未经测试,因此请检查正确性)。

编辑:由于您的数据流中还有其他数据,因此我添加了一个与下一个ZLIB启动对齐的例程。您需要在 数据中查找并填写标识ZLIB流的双字节序列。 (可以随意使用旧代码来发现它。)虽然通常没有固定的ZLIB头,但每个流应该是相同的,因为它由protocol options and flags,组成,对于整个运行来说可能是相同的。

import zlib

# FILL IN: ZHEAD is two bytes with the actual ZLIB settings in the input
ZHEAD = CMF+FLG  

def findstart(header, buf, source):
    """Find `header` in str `buf`, reading more from `source` if necessary"""

    while buf.find(header) == -1:
        more = source.read(2**12)
        if len(more) == 0:  # EOF without finding the header
            return ''
        buf += more

    offset = buf.find(header)
    return buf[offset:]

然后您可以前进到下一个流的开头。我添加了try / except对,因为在流之外可能会出现相同的字节序列:

source = open(datafile, 'rb')
skip_ = source.read(32) # Skip non-zlib header

buf = ''
while True:
    decomp = zlib.decompressobj()
    # Find the start of the next stream
    buf = findstart(ZHEAD, buf, source)
    try:    
        stream = decomp.decompress(buf)
    except zlib.error:
        print "Spurious match(?) at output offset %d." % outfile.tell(),
        print "Skipping 2 bytes"
        buf = buf[2:]
        continue

    # Read until zlib decides it's seen a complete file
    while decomp.unused_data == '':
        block = source.read(2**12)
        if len(block) > 0:       
            stream += decomp.decompress(block)
        else:
            break # We've reached EOF

    outfile.write(stream)
    buf = decomp.unused_data # Save for the next stream
    if len(block) == 0:
        break  # EOF

outfile.close()

PS 1.如果我是你,我会将每个XML流写入一个单独的文件中。

PS 2.你可以测试你在文件的第一个MB上做的任何事情,直到你获得足够的表现。

答案 1 :(得分:1)

在现代处理器(例如2 GHz i7)上解压缩833 MB应该需要大约30秒。所以,是的,你做的事情非常错误。尝试在每个字节偏移处解压缩以查看是否出现错误是问题的一部分,尽管不是全部。有更好的方法来查找压缩数据。理想情况下,您应该找到或找出格式。或者,您可以使用RFC 1950 specification搜索有效的zlib标头,但可能会出现误报。

更重要的是,您可以同时将整个833 MB读入内存,并将3 GB解压缩到内存,每次可能大块。你的机器有多少内存?你可能正在闯入虚拟内存。

如果您显示的代码有效,则数据不会压缩。 zip是一种特定的文件格式,通常带有.zip扩展名,它将原始deflate数据封装在本地和中央目录信息的结构中,用于重建文件系统中的目录。你必须有一些不同的东西,因为你的代码正在寻找并且显然正在寻找zlib流。你有什么格式?你在哪里得到它?它是如何记录的?你可以提供前100个字节的转储吗?

应该这样做的方法是将整个内容读入内存并一次解压缩整个流,也将内存解压缩。相反,请使用zlib.decompressobj接口,该接口允许您一次提供一个部分,并获得生成的可用解压缩数据。您可以用更小的部分读取输入文件,使用记录的格式查找解压缩的数据流或查找zlib(RFC 1950标头),然后通过解压缩的对象一次运行这些块,写出解压缩的数据在哪里你想要它。 decomp.unused_data可用于检测压缩流的结尾(如您找到的示例中所示)。

答案 2 :(得分:0)

根据您在评论中描述的内容,听起来他们将他们单独发送给您的各个文件连接在一起。这意味着每个人都需要跳过一个32字节的标题。

如果你没有跳过这些标题,它可能会完全符合你描述的行为:如果你运气好,你将得到32个无效标题错误,然后成功解析下一个数据流。如果你运气不好,32字节的垃圾看起来就像一个真正的流的开始,你将浪费大量的时间来解析一些任意数量的字节,直到你最终得到解码错误。 (如果你真的运气不好,那么它实际上会成功解码,给你一大堆垃圾并吃掉一个或多个后续流。)

因此,尝试在每个流完成后跳过32个字节。

或者,如果你有一种更可靠的方法来检测下一个流的开始(这就是为什么我告诉你打印偏移量并在十六进制编辑器中查看数据,而alexis告诉你看看zlib spec),改为做。