2010-11-16 77 views
12

我们已经有了一个基于Python的web服务器,它在启动时使用cPickle取消了许多大数据文件。数据文件(使用HIGHEST_PROTOCOL进行腌制)在磁盘上大约为0.4 GB,并以大约1.2 GB的Python对象的形式加载到内存中 - 大约需要20秒。我们在64位Windows机器上使用Python 2.6。如何将1GB的对象反序列化为比cPickle更快的Python对象?

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

有什么办法来反序列化超过对象成Python的1GB比cPickle快得多(如5到10倍)?因为执行时间受内存分配和对象创建的约束,所以我认为使用另一个不带钩子的技术,如JSON在这里没有帮助。

我知道有些解释的语言有一种方法来他们的整个存储器图像保存为一个磁盘文件,这样他们就可以加载它放回内存中的所有一气呵成,而无需为每个对象分配/创建。有没有办法在Python中做到这一点,或实现类似的东西?

+1

这可能是您获得固态硬盘的机会。这是为了加快开发?为了让你做快速部署? 阅读数据或取消数据的滞后性?如果你从一个空实例开始,启动时间是多少? – Scott 2010-11-16 14:59:53

+0

请注意,我在我的问题中提到的瓶颈不是驱动器/读取速度,而是取消打开和对象创建速度。对于快速部署来说更重要 - 让我们的服务器能够快速重启。我不太确定这里的“空实例”是什么意思。 – 2010-11-16 15:08:28

+0

对于一个750MB的pickle二进制文件,使用gc.disable()/ gc.enable()封装cPickle加载调用,大幅缩短了所需的总时间20倍左右。见[这里](http://stackoverflow.com/a/36699998/2385420) – 2016-04-18 17:42:03

回答

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

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

  3. 如果你的对象包含很多像numpy的阵列的原始数据,你可以记忆 - 映射他们以更快速启动。 pytables也适用于这些场景。

  4. 如果你只使用一小部分对象,那么OO数据库(如Zope的)可能会帮助你。虽然如果你在记忆中需要它们,你只会浪费很多的开销以获得小的收益。 (从来没有用过,所以这可能是无稽之谈)。

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

+0

谢谢,有帮助的东西。仅供参考,在我对包含很多对象的大文件进行快速测试时,marshal.loads()大约是pickle.loads()的两倍。 – 2010-11-16 16:37:33

+0

在一本巨大的字典中,这里的经验相同; marshal.load只需要0.78s,其中cPickle.load需要1.2s。 – unhammer 2011-08-18 12:16:06

+0

选项二会有问题,因为在您引用这些对象的那一刻,将为每个分叉的子流程复制对象。这是因为每次访问对象时,每个对象都有一个引用计数。这反过来就像改变对象并因此导致内存被复制。当涉及python时,基本上复制写入成为访问时的副本... – FableBlaze 2013-01-04 23:23:00

3

我还没有使用cPickle(或Python),但在这种情况下,我认为最好的策略是 避免不必要的对象加载,直到它们真的需要 - 比如在不同的线程上启动后加载它通常更好,以避免由于显而易见的原因在任何时候不必要的加载/初始化Google'延迟加载'或'延迟初始化'。如果你真的需要所有的对象在服务器启动之前完成一些任务,那么也许你可以尝试实现一个手动的自定义反序列化方法,换句话说,如果你对你将要处理的数据有深入的了解,就可以自己实现一些东西,这可以帮助你'挤'更好的表现,然后处理它的一般工具。

3

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

+0

好想法。但是,我们最初使用的是默认(最低)协议,但切换到HIGHEST_PROTOCOL(基于二进制的协议)将其速度提高了两倍。所以HIGHEST_PROTOCOL肯定更快。 – 2010-11-16 16:00:59

2

不可能回答这个不知道更多关于你正在装载什么样的数据以及如何使用它。

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

如果是结构化的数据,可以将它委托给一个数据库,只有拉需要什么?

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

7

您是否直接从文件加载()腌制数据?怎么样尝试将文件加载到内存中,然后执行加载? 我会开始尝试cStringIO();或者你也可以尝试编写你自己的StringIO版本,它将使用buffer()来分割内存,从而减少所需的copy()操作(cStringIO仍然可能更快,但你必须尝试)。

特别是在Windows平台上进行这类操作时,有时会出现巨大的性能瓶颈; Windows系统在某种程度上非常不优化,因为在处理很多小的读取时,UNIX可以很好地应对;如果load()会执行很多小的读操作,或者您多次调用load()来读取数据,这将有所帮助。

+1

对于给我-1的人:尝试通过在Win上调用read(1)来加载文件;然后尝试在Unix上完成。在Windows上读取几兆字节需要几秒钟的时间;在Unix上它仍然是即时的。如果benhoyt通过从一个文件调用数万个pickle.load()调用来加载大量对象,这可能是一个因素。 – ondra 2010-11-16 16:05:19

+0

良好的通话。在我们的数据上,将“obj = pickle.load(f)”改为“s = f.read(); obj = pickle.loads(s)”会使速度增加30%。没有数量级,但值得了解。 (顺便说一句,我不小心按下了,而不是up;随意对你的答案做一个小的编辑,这样我就可以upvote了。) – 2010-11-16 16:10:07

+0

我发现这里有一个相当大的改进,可以用marshal模块做同样的过程因为它是一个Windows问题)。 – 2010-11-16 18:47:22

2

我将添加另一个答案,可能是有益的 - 如果可以的话,你可以尝试定义_ 插槽 _上是最常见的类产生的?这可能会有一点限制和不可能,但是它似乎已经将测试初始化​​所需的时间缩短了大约一半。