我正在实现一个需要序列化和反序列化大型对象的程序,因此我正在使用pickle
,cPickle
和marshal
模块进行一些测试以选择最佳模块。一路上我发现了一些非常有趣的东西:
我在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
的效果也非常低效:
我猜测为什么加载marshal
这么慢的原因与它的序列化字符串的长度有点相关(比pickle
和cPickle
长得多。)
marshal
转储速度更快,加载速度更慢?marshal
序列化字符串太长了?marshal
的加载在RAM中如此低效?marshal
的装载效果?marshal
快速转储与cPickle
快速加载?答案 0 :(得分:19)
cPickle
具有比marshal
更智能的算法,并且可以通过技巧来减少大对象使用的空间。这意味着解码速度会慢,但编码速度会更快,因为结果输出更小。
marshal
是简单的,并且直接将对象序列化,而不进行任何进一步的分析。这也解释了marshal
加载效率如此低效的原因,它只需要做更多工作 - 就像从磁盘读取更多数据一样 - 能够做到与cPickle
相同的事情。
marshal
和cPickle
最终是不同的东西,你不能真正得到快速保存和快速加载,因为快速保存意味着分析数据结构较少意味着节省了大量数据到磁盘。
关于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