如何比cPickle更快地反序列化1GB对象到Python?

12 投票
6 回答
7027 浏览
提问于 2025-04-16 07:09

我们有一个基于Python的网络服务器,在启动时使用cPickle来解压多个大型数据文件。这些数据文件(使用HIGHEST_PROTOCOL进行压缩)在磁盘上大约占0.4 GB,加载到内存中后大约变成1.2 GB的Python对象——这个过程大约需要20秒。我们在64位的Windows机器上使用Python 2.6。

瓶颈显然不是磁盘(读取这么多数据只需不到0.5秒),而是内存分配和对象创建(要创建数百万个对象)。我们希望将20秒的时间缩短,以减少启动时间。

有没有办法比cPickle更快地将超过1GB的对象反序列化到Python中,比如快5到10倍?因为执行时间受限于内存分配和对象创建,我猜使用其他反序列化技术,比如JSON,可能也帮不上忙。

我知道一些解释型语言有办法将整个内存镜像保存为磁盘文件,这样它们可以一次性加载回内存,而不需要为每个对象进行分配和创建。Python中有没有类似的方法,或者能实现类似的效果?

6 个回答

4

我没有用过cPickle(或者Python),但在这种情况下,我觉得最好的办法是尽量避免在不需要的时候加载对象。比如,可以在启动后在另一个线程中加载。其实,通常来说,避免不必要的加载或初始化是个好主意,原因很明显。你可以去查一下“懒加载”或者“懒初始化”。如果你真的需要在服务器启动前加载所有对象来完成某个任务,那你可以尝试自己实现一个手动的反序列化方法。换句话说,如果你对要处理的数据非常了解,可以自己动手做一些东西,这样可能会比使用一般工具获得更好的性能。

7

你是直接从文件里加载那些经过处理的数据吗?不如试试先把文件加载到内存里,然后再进行加载?我建议你可以先试试 cStringIO;另外,你也可以尝试自己写一个版本的 StringIO,利用 buffer() 来切分内存,这样可以减少需要的 copy() 操作(虽然 cStringIO 可能会更快,但你得自己试试看)。

在进行这些操作时,有时候会遇到很大的性能瓶颈,尤其是在 Windows 系统上;因为 Windows 在处理很多小读取时表现得不太好,而 UNIX 系统就能应对得很好。如果 load() 需要进行很多小读取,或者你多次调用 load() 来读取数据,这样的改进会有帮助。

17
  1. 可以试试marshal模块——这是一个内部模块(被字节编译器使用),虽然不太被宣传,但速度非常快。需要注意的是,它不能像pickle那样序列化任意对象,只能处理一些内置类型(具体限制可以查文档)。另外,格式也不是很稳定。

  2. 如果你需要同时启动多个进程,并且可以接受有一个进程一直在运行,有个优雅的解决方案:在一个进程中加载对象,然后只在这个进程中根据需要创建其他进程。创建进程的速度很快(因为是写时复制),而且可以共享内存。 [免责声明:未经测试;与Ruby不同,Python的引用计数会触发页面复制,所以如果你有很大的对象和/或只访问其中一小部分,这可能就没什么用。]

  3. 如果你的对象里面有很多原始数据,比如numpy数组,可以使用内存映射,这样启动速度会快很多。pytables在这种情况下也很不错。

  4. 如果你只会用到对象的一小部分,那么面向对象的数据库(比如Zope的)可能会对你有帮助。不过如果你需要把所有对象都加载到内存里,那就会浪费很多资源,收效却很小。(我没用过这种数据库,所以这可能是胡说。)

  5. 也许其他的Python实现可以做到?我不知道,只是个想法……

撰写回答