marshal 序列化更快,cPickle 反序列化更快
我正在实现一个需要对大对象进行序列化和反序列化的程序,所以我用 pickle
、cPickle
和 marshal
模块做了一些测试,以便选择最合适的模块。在这个过程中,我发现了一些很有趣的事情:
我使用 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 倍。
至于内存方面,marshal
在 导入 时的表现也非常低效:
我猜 marshal
导入速度慢的原因可能和它序列化后的字符串长度有关(比 pickle
和 cPickle
都要长得多)。
- 为什么
marshal
导出快而导入慢? - 为什么
marshal
的序列化字符串这么长? - 为什么
marshal
的导入在内存中这么低效? - 有没有办法提高
marshal
的导入性能? - 有没有办法把
marshal
的快速导出和cPickle
的快速导入结合起来?
6 个回答
这些基准测试之间的差异给我们提供了一个加速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
在第一个例子中,列表里重复了同一个字符串。而第二个列表虽然看起来一样,但每个字符串都是一个独立的对象,因为它们是通过某个表达式生成的。如果你最开始是从外部来源读取数据,那么你可以考虑进行一些字符串去重的操作。
有些人可能觉得这方法有点不太正规,但我用这个方法取得了很好的效果。具体来说,我在进行数据保存时,把调用的部分用gc.disable()和gc.enable()包裹起来。比如,下面的代码片段中,写入一个大约50MB的字典列表,从原来的78秒缩短到了4秒。
# not a complete example....
gc.disable()
cPickle.dump(params,fout,cPickle.HIGHEST_PROTOCOL)
fout.close()
gc.enable()
cPickle
的算法比 marshal
更聪明,能够通过一些技巧来减少大对象所占用的空间。这意味着它在解码时会慢一些,但在编码时会快,因为生成的输出更小。
marshal
则比较简单,它直接把对象保存成原样,没有进行进一步的分析。这也解释了为什么 marshal
加载时效率低,因为它需要做更多的工作——也就是从磁盘读取更多的数据——才能做到和 cPickle
一样的事情。
最后,marshal
和 cPickle
是完全不同的东西,你不能同时实现快速保存和快速加载,因为快速保存意味着对数据结构的分析少,这样就会把很多数据保存到磁盘上。
关于 marshal
可能与其他版本的 Python 不兼容的问题,通常建议使用 cPickle
:
“这不是一个通用的‘持久化’模块。对于 Python 对象的通用持久化和通过 RPC 调用进行传输,请查看模块 pickle 和 shelve。marshal 模块主要用于支持读取和写入 Python 模块的‘伪编译’代码,也就是 .pyc 文件。因此,Python 的维护者保留在必要时以向后不兼容的方式修改 marshal 格式的权利。如果你要序列化和反序列化 Python 对象,请使用 pickle 模块——性能相当,版本独立性有保障,并且 pickle 支持的对象范围远远超过 marshal。” (关于 marshal 的 Python 文档)