marshal转储速度更快,cPickle加载速度更快

时间:2011-12-15 01:28:40

标签: python performance serialization

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

我在dicts,元组,整数,浮点数和字符串列表中使用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的效果也非常低效:

Ubuntu System Monitor

我猜测为什么加载marshal这么慢的原因与它的序列化字符串的长度有点相关(比picklecPickle长得多。)

  • 为什么marshal转储速度更快,加载速度更慢?
  • 为什么marshal序列化字符串太长了?
  • 为什么marshal的加载在RAM中如此低效?
  • 有没有办法提高marshal的装载效果?
  • 有没有办法合并marshal快速转储与cPickle快速加载?

6 个答案:

答案 0 :(得分:19)

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

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

关于marshal可能与其他版本的Python不兼容的事实,您通常应该使用cPickle

  

“这不是一般的”持久性“模块。对于通过RPC调用的Python对象的一般持久性和传递,请参阅模块pickle和shelve。编组模块主要用于支持读写”伪编译“代码对于.pyc文件的Python模块。因此,Python维护者保留在需要时以反向不兼容的方式修改编组格式的权利。如果您要对Python对象进行序列化和反序列化,请使用pickle模块 - 性能具有可比性,版本独立性得到保证,并且pickle支持比marshal更广泛的对象。“ (the python docs about marshal

答案 1 :(得分:13)

有些人可能会认为这太过分了,但我通过简单地用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()

答案 2 :(得分:9)

这些基准测试之间的区别为加速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

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

答案 3 :(得分:5)

你可以制作cPickle cca。通过创建cPickle.Pickler实例然后将未记录的选项'fast'设置为1来快50倍(!):

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

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

答案 4 :(得分:3)

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

关于marshal可能与其他版本的Python不兼容的事实,您通常应该使用cPickle

  

“这不是一般的”持久性“模块。对于通过RPC调用的Python对象的一般持久性和传递,请参阅模块pickle和shelve。编组模块主要用于支持读写”伪编译“代码对于.pyc文件的Python模块。因此,Python维护者保留在需要时以反向不兼容的方式修改编组格式的权利。如果您要对Python对象进行序列化和反序列化,请使用pickle模块 - 性能具有可比性,版本独立性得到保证,并且pickle支持比marshal更广泛的对象。“ (the python docs about marshal

答案 5 :(得分:3)

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

我的预感是,压缩数据并将其输入到反序列化中比通过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