【问题标题】:marshal dumps faster, cPickle loads faster编组转储更快,cPickle 加载更快
【发布时间】:2012-01-20 18:58:30
【问题描述】:

我正在实现一个需要对大型对象进行序列化和反序列化的程序,因此我对picklecPicklemarshal 模块进行了一些测试,以选择最佳模块。一路走来,我发现了一些非常有趣的事情:

我在字典、元组、整数、浮点数和字符串列表中使用dumps,然后使用loads(对于每个模块)。

这是我的基准测试的输出:

DUMPING a list of length 7340032
----------------------------------------------------------------------
pickle => 14.675 seconds
length of pickle serialized string: 31457430

cPickle => 2.619 seconds
length of cPickle serialized string: 31457457

marshal => 0.991 seconds
length of marshal serialized string: 117440540

LOADING a list of length: 7340032
----------------------------------------------------------------------
pickle => 13.768 seconds
(same length?) 7340032 == 7340032

cPickle => 2.038 seconds
(same length?) 7340032 == 7340032

marshal => 6.378 seconds
(same length?) 7340032 == 7340032

因此,从这些结果中我们可以看到marshal 在基准测试的转储部分非常快:

pickle 快 14.8 倍,比 cPickle 快 2.6 倍。

但是,让我大吃一惊的是,marshal加载 部分比 cPickle 慢得多:

pickle 快 2.2 倍,但比 cPickle 慢 3.1 倍。

而对于 RAM,marshal加载时的性能也非常低效:

我猜想使用marshal 加载如此缓慢的原因在某种程度上与其序列化字符串的长度有关(比picklecPickle 长得多)。

  • 为什么marshal 转储更快而加载更慢?
  • 为什么marshal序列化的字符串那么长?
  • 为什么marshal 的加载在 RAM 中效率如此之低?
  • 有没有办法提高marshal的加载性能?
  • 有没有办法将marshal快速转储与cPickle快速加载合并?

【问题讨论】:

  • 你的问题是死胡同。 marshal 模块并不打算用作pickle 的替代品。没有关于 marshal 文件格式的官方文档,它可能会因版本而异,因此您的基准测试结果将来可能是错误的。
  • 在核心,您的问题完全相同:为什么 marshal 更快?速度/内存使用是计算中通常的权衡。是的,你的问题没有用。当然,这只是我的看法。
  • 不保证现在由 marshal 创建的文件可以被所有未来版本的 Python 读取。你的研究毫无意义。

标签: python performance serialization


【解决方案1】:

cPickle 具有比marshal 更智能的算法,并且能够通过一些技巧来减少大型对象使用的空间。这意味着解码速度会更慢,但编码速度会更快,因为结果输出更小。 marshal 非常简单,直接按原样序列化对象,而不做任何进一步的分析。这也解释了为什么 marshal 加载效率如此之低,它只需要做更多的工作——比如从磁盘读取更多数据——才能做与 cPickle 相同的事情。

marshalcPickle 最终是真正不同的东西,你不能真正获得快速保存和快速加载,因为快速保存意味着更少分析数据结构,这意味着将大量数据保存到磁盘。

关于marshal可能与其他版本的Python不兼容,一般应该使用cPickle

"这不是一个通用的“持久化”模块。对于Python对象通过RPC调用的通用持久化和传输,请参见模块pickle和shelve。marshal模块的存在主要是为了支持读写“伪编译”代码对于 .pyc 文件的 Python 模块。因此,如果需要,Python 维护者保留以向后不兼容的方式修改 marshal 格式的权利。如果您正在序列化和反序列化 Python 对象,请改用 pickle 模块 - 性能具有可比性,保证了版本独立性,并且 pickle 支持的对象范围比 marshal 大得多。” (the python docs about marshal)

【讨论】:

    【解决方案2】:

    如您所见,cPickle.dump 产生的输出长度约为marshal.dump 产生的输出长度的 1/4。这意味着cPickle 必须使用更复杂的算法来转储数据,因为不需要的东西被删除。加载转储列表时,marshal 必须处理更多数据,而cPickle 可以快速处理其数据,因为需要分析的数据更少。

    关于marshal可能与其他版本的Python不兼容,一般应该使用cPickle

    "这不是一个通用的“持久化”模块。对于Python对象通过RPC调用的通用持久化和传输,请参见模块pickle和shelve。marshal模块的存在主要是为了支持读写“伪编译”代码对于 .pyc 文件的 Python 模块。因此,如果需要,Python 维护者保留以向后不兼容的方式修改 marshal 格式的权利。如果您正在序列化和反序列化 Python 对象,请改用 pickle 模块 - 性能具有可比性,保证了版本独立性,并且 pickle 支持的对象范围比 marshal 大得多。” (the python docs about marshal)

    【讨论】:

      【解决方案3】:

      这些基准之间的差异为加速 cPickle 提供了一个思路:

      Input: ["This is a string of 33 characters" for _ in xrange(1000000)]
      cPickle dumps 0.199 s loads 0.099 s 2002041 bytes
      marshal dumps 0.368 s loads 0.138 s 38000005 bytes
      
      Input: ["This is a string of 33 "+"characters" for _ in xrange(1000000)]
      cPickle dumps 1.374 s loads 0.550 s 40001244 bytes
      marshal dumps 0.361 s loads 0.141 s 38000005 bytes
      

      在第一种情况下,列表重复相同的字符串。第二个列表是等价的,但每个字符串都是一个单独的对象,因为它是表达式的结果。现在,如果您最初是从外部源读取数据,则可以考虑某种字符串重复数据删除。

      【讨论】:

        【解决方案4】:

        有些人可能认为这太过分了,但我通过简单地用 gc.disable() 和 gc.enable() 包装 pickle 转储调用取得了巨大的成功。例如,下面编写约 50MB 字典列表的片段从 78 秒缩短到 4 秒。

        #  not a complete example....
        gc.disable()
        cPickle.dump(params,fout,cPickle.HIGHEST_PROTOCOL)         
        fout.close()               
        gc.enable()
        

        【讨论】:

        • 这很完美!对我来说,所需的总时间也减少了 20 倍。虽然@Chris,你能指出我们的任何影响(如果有的话)吗?
        • @tdc,Tejas,您将无法再转储非循环对象,例如如果启用了 Pickler.fast,x = []; x.append(x) 中的 x 将导致 ValueError。
        • 负载怎么样?
        【解决方案5】:

        您可以制作 cPickle cca。通过创建 cPickle.Pickler 实例,然后将未记录的选项“快速”设置为 1,速度提高了 50 倍(!):

        outfile = open('outfile.pickle')
        fastPickler = cPickle.Pickler(outfile, cPickle.HIGHEST_PROTOCOL)
        fastPickler.fast = 1
        fastPickler.dump(myHugeObject)
        outfile.close()
        

        但是如果你的 myHugeObject 有循环引用,dump 方法将永远不会结束。

        【讨论】:

        • 知道有用!它是否也让load 更快?
        • 我不这么认为,快速选项仅在酸洗数据时禁用重复子对象检测。您可以在 python 3 系列文档 (docs.python.org/3/library/…) 中找到更多信息,当然也可以在代码中找到
        • 仅适用于 Python3。
        【解决方案6】:

        您可以通过压缩序列化结果来提高存储效率。

        我的预感是压缩数据并将其输入反序列化会比通过 HDD 从磁盘读取原始数据更快。

        进行了以下测试以证明压缩会加快反序列化过程。 由于机器配备了SSD,结果不如预期。 在配备 HHD 的机器上,使用 lz4 压缩数据会更快,因为从磁盘读取的平均速度在 60-70mb/s 之间。

        LZ4:速度降低 18% 时,压缩产生 77.6% 的额外存储空间。

        marshal - compression speed time
        Bz2 7.492605924606323  10363490
        Lz4 1.3733329772949219 46018121
        --- 1.126852035522461 205618472
        cPickle - compression speed time
        Bz2 15.488649845123291 10650522
        Lz4 9.192650079727173  55388264
        --- 8.839831113815308 204340701
        

        【讨论】:

        • 有趣的结果!您是否暗示您以某种方式避免在反序列化之前必须解压缩数据?如果有,怎么做?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-11
        相关资源
        最近更新 更多