【问题标题】:Decreasing the size of cPickle objects减小 cPickle 对象的大小
【发布时间】:2013-08-27 20:27:40
【问题描述】:

我正在运行创建大型对象的代码,其中包含多个用户定义的类,然后我必须对其进行序列化以供以后使用。据我所知,只有酸洗才能满足我的要求。我一直在使用 cPickle 来存储它们,但它生成的对象大小约为 40G,来自运行在 500 mb 内存中的代码。序列化的速度不是问题,但对象的大小才是问题。有什么技巧或替代方法可以让泡菜变小吗?

【问题讨论】:

  • 你用的是什么pickle协议?
  • 协议版本 0。2 会有很大的不同吗?
  • 应该会有所不同。不过,我不确定多少钱。
  • 我认为协议 2 会有很大的不同,值得一试。将它与压缩与gzipbzip2 结合使用以产生更大的差异。我会对数值结果感兴趣。
  • 从 40 降到 12.2!我阅读的文档似乎不会产生如此大的影响,所以我很惊讶。之前压缩是不够的,但现在可以了。

标签: python serialization pickle


【解决方案1】:

您可以将您的 cPickle dump 调用与一个 zip 文件结合起来:

import cPickle
import gzip

def save_zipped_pickle(obj, filename, protocol=-1):
    with gzip.open(filename, 'wb') as f:
        cPickle.dump(obj, f, protocol)

然后重新加载一个压缩的腌制对象:

def load_zipped_pickle(filename):
    with gzip.open(filename, 'rb') as f:
        loaded_object = cPickle.load(f)
        return loaded_object

【讨论】:

  • 可以添加 load() 函数来完成吗?
  • @ealeon 添加了一个,希望它是你所追求的。
【解决方案2】:

如果您必须使用 pickle 并且没有其他适合您的序列化方法,您始终可以通过 bzip2 管道处理 pickle。唯一的问题是bzip2 有点慢……gzip 应该更快,但文件大小几乎是原来的 2 倍:

In [1]: class Test(object):
            def __init__(self):
                self.x = 3841984789317471348934788731984731749374
                self.y = 'kdjsaflkjda;sjfkdjsf;klsdjakfjdafjdskfl;adsjfl;dasjf;ljfdlf'
        l = [Test() for i in range(1000000)]

In [2]: import cPickle as pickle          
        with open('test.pickle', 'wb') as f:
            pickle.dump(l, f)
        !ls -lh test.pickle
-rw-r--r--  1 viktor  staff    88M Aug 27 22:45 test.pickle

In [3]: import bz2
        import cPickle as pickle
        with bz2.BZ2File('test.pbz2', 'w') as f:
            pickle.dump(l, f)
        !ls -lh test.pbz2
-rw-r--r--  1 viktor  staff   2.3M Aug 27 22:47 test.pbz2

In [4]: import gzip
        import cPickle as pickle
        with gzip.GzipFile('test.pgz', 'w') as f:
            pickle.dump(l, f)
        !ls -lh test.pgz
-rw-r--r--  1 viktor  staff   4.8M Aug 27 22:51 test.pgz

所以我们看到bzip2 的文件大小几乎小了 40 倍,gzip 小了 20 倍。如您所见,gzip 在性能上与原始 cPickle 非常接近:

cPickle : best of 3: 18.9 s per loop
bzip2   : best of 3: 54.6 s per loop
gzip    : best of 3: 24.4 s per loop

【讨论】:

  • 你没有考虑 lzma,我发现这是一个非常好的算法。当我使用 lzma 压缩 200000 个随机数的腌制列表时,它击败了 gzip 和 bzip2(至少在大小上,我没有检查速度)
  • @Viktor 有没有比 cpickle 更快的序列化? (你写“没有其他方法适合你”)
  • @DreamFlasher 还有其他更简单的序列化模块如msgpack、json……但它们不会序列化复杂的python对象,只是基本类型。
  • @ViktorKerkez 我知道这些,一直在寻找加载速度最快的,在这方面泡菜是最快的。但是您对手头的问题是正确的,这些本来是替代品(尽管我认为它们不会更小)。
  • 为什么不在压缩文件上加上'wb'?
【解决方案3】:

您可能希望使用更高效的酸洗协议。

截至目前,共有三个pickle protocols

  • 协议版本 0 是原始 ASCII 协议,向后兼容早期版本的 Python。
  • 协议版本 1 是旧的二进制格式,它也与早期版本的 Python 兼容。
  • 协议版本 2 是在 Python 2.3 中引入的。它提供了更有效的新式类的酸洗。

此外,默认是协议 0,效率最低的一个:

如果未指定协议,则使用协议 0。如果协议指定为负值或 HIGHEST_PROTOCOL,将使用可用的最高协议版本。

让我们检查一下使用最新协议(目前是协议 2(最有效的协议))和使用协议 0(默认)之间的大小差异,作为任意示例。请注意,我在这里使用 protocol=-1 以确保我们始终使用最新的协议,并且我导入 cPickle 以确保我们使用更快的 C 实现:

import numpy
from sys import getsizeof
import cPickle as pickle

# Create list of 10 arrays of size 100x100
a = [numpy.random.random((100, 100)) for _ in xrange(10)]

# Pickle to a string in two ways
str_old = pickle.dumps(a, protocol=0)
str_new = pickle.dumps(a, protocol=-1)

# Measure size of strings
size_old = getsizeof(str_old)
size_new = getsizeof(str_new)

# Print size (in kilobytes) using old, using new, and the ratio
print size_old / 1024.0, size_new / 1024.0, size_old / float(size_new)

我得到的打印输出是:

2172.27246094 781.703125 2.77889698975

表示用旧协议酸洗用了2172KB,用新协议酸洗用了782KB,相差x2.8倍。请注意,此因素特定于此示例 - 您的结果可能会有所不同,具体取决于您正在酸洗的对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    相关资源
    最近更新 更多