12

我们有一个基于 Python 的 Web 服务器,它在启动时使用cPickle. 数据文件(使用 腌制HIGHEST_PROTOCOL)在磁盘上大约为 0.4 GB,并作为大约 1.2 GB 的 Python 对象加载到内存中——这大约需要20 秒。我们在 64 位 Windows 机器上使用 Python 2.6。

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

有什么方法可以将超过 1GB 的对象反序列化成 Python 比cPickle(比如 5-10 倍)快得多?因为执行时间受内存分配和对象创建的限制,所以我认为使用另一种 unpickling 技术(例如 JSON)在这里没有帮助。

我知道一些解释语言有办法将它们的整个内存映像保存为磁盘文件,因此他们可以一次将其加载回内存,而无需为每个对象分配/创建。有没有办法在 Python 中做到这一点,或者实现类似的东西?

4

6 回答 6

17
  1. 尝试 marshal 模块 - 它是内部的(由字节编译器使用)并且故意不做太多宣传,但它要快得多。请注意,它不会序列化像pickle这样的任意实例,只有内置类型(不记得确切的约束,请参阅文档)。另请注意,格式不稳定。

  2. 如果您需要初始化多个进程并且可以容忍始终加载一个进程,那么有一个优雅的解决方案:在一个进程中加载​​对象,然后除了按需分叉进程之外什么都不做。分叉速度很快(写入时复制)并在所有进程之间共享内存。[免责声明:未经测试;与 Ruby 不同,Python 引用计数将触发页面复制,因此如果您有巨大的对象和/或访问其中的一小部分,这可能是无用的。]

  3. 如果您的对象包含大量原始数据,例如 numpy 数组,您可以对它们进行内存映射以加快启动速度。pytables 也适用于这些场景。

  4. 如果您只使用一小部分对象,那么 OO 数据库(如 Zope 的)可能会帮助您。尽管如果您需要将它们全部存储在内存中,那么您只会浪费大量开销而收效甚微。(从未使用过,所以这可能是胡说八道)。

  5. 也许其他python实现可以做到?不知道,只是一个想法...

于 2010-11-16T15:32:07.207 回答
7

您是否直接从文件中加载()腌制数据?尝试将文件加载到内存中然后进行加载呢?我会先尝试 cStringIO(); 或者,您可以尝试编写自己的 StringIO 版本,该版本将使用 buffer() 对内存进行切片,从而减少所需的 copy() 操作(cStringIO 仍然可能更快,但您必须尝试)。

在进行此类操作时,有时会出现巨大的性能瓶颈,尤其是在 Windows 平台上;不知何故,Windows 系统在进行大量小读取时非常未优化,而 UNIX 则可以很好地应对;如果 load() 进行大量小读取,或者您多次调用 load() 来读取数据,这会有所帮助。

于 2010-11-16T15:30:35.537 回答
4

我没有使用cPickle(或Python),但在这种情况下,我认为最好的策略是避免不必要地加载对象,直到真正需要它们 - 比如说在不同线程上启动后加载,实际上通常最好避免出于显而易见的原因,随时进行不必要的加载/初始化。谷歌“延迟加载”或“延迟初始化”。如果您确实需要所有对象在服务器启动之前执行某些任务,那么也许您可以尝试实现手动自定义反序列化方法,换句话说,如果您对要处理的数据有深入的了解,可以自己实现一些东西,这可以帮助您'挤压'比处理它的一般工具更好的性能。

于 2010-11-16T14:43:04.717 回答
3

您是否尝试通过不使用 HIGHEST_PROTOCOL 来牺牲酸洗效率?目前尚不清楚使用此协议与哪些性能成本相关,但可能值得一试。

于 2010-11-16T15:09:31.320 回答
2

如果不了解更多有关您正在加载的数据类型以及如何使用它的信息,就不可能回答这个问题。

如果是某种业务逻辑,也许你应该试着把它变成一个预编译的模块;

如果它是结构化数据,您可以将其委托给数据库并仅提取需要的数据吗?

数据是否有规律的结构?有没有办法将它划分并决定需要什么,然后才加载它?

于 2010-11-16T15:17:52.700 回答
2

我将添加另一个可能会有所帮助的答案 - 如果可以,您可以尝试在最常创建的类上定义 _ slot _ 吗?这可能有点限制和不可能,但是它似乎已将我的测试初始化​​所需的时间减少到大约一半。

于 2010-11-16T16:29:56.227 回答