什么是压缩序列化Python数据的最节省空间的方法?

36

来自Python文档

默认情况下,pickle数据格式使用相对紧凑的二进制表示。如果您需要最佳尺寸特性,则可以有效地压缩pickled数据。

在运行几个小时的过程的最后,我将序列化数千兆字节的数据,并希望结果在磁盘上尽可能小。但是,Python提供了几种不同的压缩数据的方式

这些方法中有一种特别适用于pickled文件吗?我要pickling的数据主要由嵌套的字典和字符串组成,因此如果有一种更高效的方法可以压缩JSON等数据,那么也可以使用。

压缩和解压缩所需的时间并不重要,但是生成数据所需的时间使得试错变得不方便。


https://dev59.com/gm865IYBdhLWcg3wCqHI - Gabriel Cappelli
@GabrielC 谢谢!我特别想知道Python的pickling是否针对某种特定类型的压缩进行了优化,因为文档中提到“您可以有效地压缩pickled数据”;例如,格式中可能存在一些模式,其中一个压缩算法可以利用而另一个不能。 - Draconis
4个回答

42

我使用了一个Pickled对象进行了一些测试,lzma 压缩效果最好。

但是你的结果可能会因你的数据而异,我建议使用自己的一些样例数据进行测试。

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/17/2019  10:05 PM       23869925 no_compression.pickle
-a----        9/17/2019  10:06 PM        6050027 gzip_test.gz
-a----        9/17/2019  10:06 PM        3083128 bz2_test.pbz2
-a----        9/17/2019  10:07 PM        1295013 brotli_test.bt
-a----        9/17/2019  10:06 PM        1077136 lzma_test.xz

使用的测试文件(您需要pip安装brotli或删除该算法):

import bz2
import gzip
import lzma
import pickle

import brotli


class SomeObject():

    a = 'some data'
    b = 123
    c = 'more data'

    def __init__(self, i):
        self.i = i


data = [SomeObject(i) for i in range(1, 1000000)]

with open('no_compression.pickle', 'wb') as f:
    pickle.dump(data, f)

with gzip.open("gzip_test.gz", "wb") as f:
    pickle.dump(data, f)

with bz2.BZ2File('bz2_test.pbz2', 'wb') as f:
    pickle.dump(data, f)

with lzma.open("lzma_test.xz", "wb") as f:
    pickle.dump(data, f)

with open('no_compression.pickle', 'rb') as f:
    pdata = f.read()
    with open('brotli_test.bt', 'wb') as b:
        b.write(brotli.compress(pdata))

我看到Brotli文件是在从pickled文件中读取后写入的。有什么原因吗? - undefined

13

我想提供一种极高的压缩比方案,它为我提供了最高的压缩比,并且速度非常快,以至于我确信我一定犯了错误(事实并非如此)。真正的好处在于解压缩也非常快,所以任何读取大量预处理数据的程序都会受益匪浅。一个可能需要注意的问题是这里提到了“小数组(<2GB)”,但看起来有几种方法可以解决。或者,如果您像我一样懒得动手,那么通常可以选择将您的数据分开。

一些聪明的人想出了python-blosc。据他们的文档称,它是一款“高性能压缩器”。我从这个问题的答案中了解到了它。

一旦通过例如pip install bloscconda install python-blosc安装完成,您可以轻松地压缩拾取的数据,如下所示:

import blosc
import numpy as np
import pickle

data = np.random.rand(3, 3, 1e7)

pickled_data = pickle.dumps(data)  # returns data as a bytes object
compressed_pickle = blosc.compress(pickled_data)

with open("path/to/file/test.dat", "wb") as f:
    f.write(compressed_pickle)

并且要阅读它:

with open("path/to/file/test.dat", "rb") as f:
    compressed_pickle = f.read()

depressed_pickle = blosc.decompress(compressed_pickle)
data = pickle.loads(depressed_pickle)  # turn bytes object back into data

我正在使用Python 3.7。即使没有查看所有不同的压缩选项,我也获得了大约12的压缩比率,并且读取+解压缩+加载经过压缩的pickle文件所需的时间比加载未经压缩的pickle文件多了一小部分秒。

我写这篇文章更多是作为自己的参考,但我希望其他人也能从中受益。

愿和平相伴。


1
答案中的文档链接已经失效。这个链接可以使用:link - Rodney Dunning

2
我认为“高效压缩泡菜数据”是指通用压缩器往往表现良好。但 Pickle 是一个协议,而不是一个具体的格式。通过在自定义类上实现"__reduce__"方法,可以使 Pickle 发出经过压缩的字节串。尝试进一步压缩这些字节串将不起作用。
在标准库压缩器中,LZMA 在典型数据流上通常具有最佳比率,但它也是最慢的。您可能可以使用 ZPAQ(例如通过 "pyzpaq")获得更好的效果,但这会更慢。

1

mgzip是一个更快的解决方案。 虽然它的压缩比比mgzip高约25%,但lzma非常缓慢。

with mgzip.open(pathname, 'wb') as f:
    pickle.dump(data, f)

加载:

with mgzip.open(pathname, 'rb') as f:
    data = pickle.load(f)

在@miterhen的回答中,“mgzip”与“pickle + blosc”方法相比如何? - FullMetalScientist
1
@FullMetalScientist,对于这个特定的例子,pickle+blocs是双倍快(07.639222秒对15.569187秒),并且提供了一个稍微更好压缩的文件(601.3MB对646.8MB)。 - Tedo Vrbanec
1
@FullMetalScientist,但在我的实际需求中,mgzip更快。这是肯定的,因为我已经尝试了pickle+blosc并将其注释掉了。我会再试一次的。 ;) - Tedo Vrbanec
1
@FullMetalScientist,我最终再次检查了一下,胜利者是pickle+blosc! - Tedo Vrbanec
今天我在使用pickle + blosc时遇到了一个错误。blosc2/core.py的第28行_check_input_length中,抛出了ValueError异常:“%s不能大于%d字节” %(input_name,blosc2_ext.MAX_BUFFERSIZE)。 ValueError: src不能大于2147483615字节 [是的,相当大的数据/文件。 :)]因此,我将来会使用mgzip。 - Tedo Vrbanec

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