6

我有一个使用巨大字典全局变量的 python 模块,目前我将计算代码放在顶部,每次第一次导入或重新加载模块需要超过一分钟,这是完全不可接受的。如何将计算结果保存在某处,以便下次导入/重新加载不必计算它?我尝试了 cPickle,但从文件(1.3M)加载字典变量的时间与计算时间大致相同。

要提供有关我的问题的更多信息,

FD = FreqDist(word for word in brown.words()) # this line of code takes 1 min
4

13 回答 13

17

只是为了澄清:模块主体中的代码不会在每次导入模块时执行 - 它只运行一次,之后未来的导入会找到已经创建的模块,而不是重新创建它。查看 sys.modules 以查看缓存模块的列表。

但是,如果您的问题是程序运行后第一次导入所需的时间,您可能需要使用 python dict 以外的其他方法。可能最好使用磁盘形式,例如 sqlite 数据库,dbm 模块之一。

对于界面的最小更改,搁置模块可能是您的最佳选择 - 这在 dbm 模块之间放置了一个非常透明的界面,使它们像任意 python dict 一样,允许存储任何可挑选的值。这是一个例子:

# Create dict with a million items:
import shelve
d = shelve.open('path/to/my_persistant_dict')
d.update(('key%d' % x, x) for x in xrange(1000000))
d.close()

然后在下一个过程中,使用它。应该不会有很大的延迟,因为仅对磁盘表单上请求的键执行查找,因此不必将所有内容加载到内存中:

>>> d = shelve.open('path/to/my_persistant_dict')
>>> print d['key99999']
99999

它比真正的字典要慢一些,如果执行需要所有键的操作(例如尝试打印它),仍然需要很长时间才能加载,但可能会解决您的问题。

于 2008-10-12T20:12:25.073 回答
4

在第一次使用时计算您的全局变量。

class Proxy:
    @property
    def global_name(self):
        # calculate your global var here, enable cache if needed
        ...

_proxy_object = Proxy()
GLOBAL_NAME = _proxy_object.global_name

或者更好的是,通过特殊的数据对象访问必要的数据。

class Data:
    GLOBAL_NAME = property(...)

data = Data()

例子:

from some_module import data

print(data.GLOBAL_NAME)

请参阅Django 设置

于 2008-10-12T17:29:46.163 回答
2

我假设您已将 dict 文字粘贴到源代码中,这需要一分钟吗?我不知道如何解决这个问题,但你可能会避免在导入时实例化这个字典......你可以在第一次实际使用它时懒惰地实例化它。

于 2008-10-12T16:14:43.523 回答
2

您可以尝试使用marshal模块而不是 c?Pickle one;它可能会更快。python使用该模块以二进制格式存储值。请特别注意以下段落,以查看 marshal 是否符合您的需求:

并非所有 Python 对象类型都受支持;通常,只有值与 Python 的特定调用无关的对象才能被此模块写入和读取。支持以下类型:无、整数、长整数、浮点数、字符串、Unicode 对象、元组、列表、集合、字典和代码对象,应理解,元组、列表和字典仅支持 long因为其中包含的价值观本身是受支持的;并且不应该编写递归列表和字典(它们会导致无限循环)。

为了安全起见,在解组 dict 之前,请确保解组 dict 的 Python 版本与执行编组的 Python 版本相同,因为不能保证向后兼容性。

于 2008-10-12T17:38:20.500 回答
2

如果“搁置”解决方案太慢或太繁琐,还有其他可能性:

于 2008-12-06T01:56:12.390 回答
2

shelve使用大型数据集变得非常慢。我一直在非常成功地使用redis ,并围绕它编写了一个FreqDist 包装器。它非常快,并且可以同时访问。

于 2009-07-26T15:28:23.640 回答
1

您可以使用搁板将数据存储在磁盘上,而不是将整个数据加载到内存中。所以启动时间会非常快,但代价是访问时间会变慢。

Shelve 也会腌制 dict 值,但不会在启动时对所有项目进行(取消)腌制,而只会在每个项目本身的访问时进行。

于 2008-10-12T17:04:20.910 回答
1

一些有助于加快进口的事情:

  1. 在运行 python 时,您可以尝试使用 -OO 标志运行 python。这将进行一些优化,以减少模块的导入时间。
  2. 是否有任何理由不能将字典分解为可以更快加载的单独模块中的较小字典?
  3. 作为最后的手段,您可以异步进行计算,这样它们就不会延迟您的程序,直到它需要结果。或者,如果您想利用多核架构,甚至可以将字典放在单独的进程中并使用 IPC 来回传递数据。

话虽如此,我同意您在第一次导入模块后不应该遇到任何延迟。以下是其他一些一般性想法:

  1. 您是否在函数中导入模块?如果是这样,这可能会导致性能问题,因为它必须在每次遇到 import 语句时检查并查看模块是否已加载。
  2. 你的程序是多线程的吗?我曾见过在多线程应用程序中导入模块时执行代码会导致一些不稳定和应用程序不稳定的情况(最明显的是 cgitb 模块)。
  3. 如果这是一个全局变量,请注意全局变量查找时间可能比局部变量查找时间长得多。在这种情况下,如果您在同一上下文中多次使用字典,则可以通过将字典绑定到局部变量来显着提高性能。

话虽如此,在没有更多背景信息的情况下给你任何具体建议有点困难。更具体地说,您在哪里导入它?计算是什么?

于 2008-10-12T22:31:37.210 回答
1
  1. 将计算密集型部分分解为单独的模块。然后至少在重新加载时,您不必等待。

  2. 尝试使用协议 2 转储数据结构。尝试的命令是cPickle.dump(FD, protocol=2). 从文档字符串中cPickle.Pickler

    Protocol 0 is the
    only protocol that can be written to a file opened in text
    mode and read back successfully.  When using a protocol higher
    than 0, make sure the file is opened in binary mode, both when
    pickling and unpickling. 
    
于 2008-10-14T13:07:08.347 回答
1

我正在经历同样的问题......搁置,数据库等......对于这类问题来说都太慢了。您需要接受一次打击,将其插入到 Redis 等内存键/值存储中。它只会存在于内存中(警告它可能会占用大量内存,因此您可能需要一个专用的盒子)。您永远不必重新加载它,您只需在内存中查找密钥

r = Redis()
r.set(key, word)

word = r.get(key)
于 2010-02-06T16:28:31.870 回答
0

扩展延迟计算的想法,为什么不把 dict 变成一个根据需要提供(和缓存)元素的类呢?

您还可以使用 psyco 来加快整体执行速度……

于 2008-10-12T17:54:36.753 回答
0

或者您可以只使用数据库来存储值?查看 SQLObject,这使得将内容存储到数据库变得非常容易。

于 2008-10-12T18:08:54.853 回答
0

对于这个问题,还有另一个非常明显的解决方案。重新加载代码时,原始范围仍然可用。

所以......做这样的事情将确保这段代码只执行一次。

try:
    FD
except NameError:
    FD = FreqDist(word for word in brown.words())
于 2009-01-10T18:06:13.157 回答