我有一个使用巨大字典全局变量的 python 模块,目前我将计算代码放在顶部,每次第一次导入或重新加载模块需要超过一分钟,这是完全不可接受的。如何将计算结果保存在某处,以便下次导入/重新加载不必计算它?我尝试了 cPickle,但从文件(1.3M)加载字典变量的时间与计算时间大致相同。
要提供有关我的问题的更多信息,
FD = FreqDist(word for word in brown.words()) # this line of code takes 1 min
只是为了澄清:模块主体中的代码不会在每次导入模块时执行 - 它只运行一次,之后未来的导入会找到已经创建的模块,而不是重新创建它。查看 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
它比真正的字典要慢一些,如果您执行需要所有键的操作(例如尝试打印它),仍然需要很长时间才能加载,但可能会解决您的问题。
在第一次使用时计算您的全局变量。
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 设置。
我假设您已将 dict 文字粘贴到源代码中,这需要一分钟吗?我不知道如何解决这个问题,但你可能会避免在导入时实例化这个字典......你可以在第一次实际使用它时懒惰地实例化它。
您可以尝试使用marshal模块而不是 c?Pickle one;它可能会更快。python使用该模块以二进制格式存储值。请特别注意以下段落,以查看 marshal 是否符合您的需求:
并非所有 Python 对象类型都受支持;通常,只有值与 Python 的特定调用无关的对象才能被此模块写入和读取。支持以下类型:无、整数、长整数、浮点数、字符串、Unicode 对象、元组、列表、集合、字典和代码对象,应理解,元组、列表和字典仅支持 long因为其中包含的价值观本身是受支持的;并且不应该编写递归列表和字典(它们会导致无限循环)。
为了安全起见,在解组 dict 之前,请确保解组 dict 的 Python 版本与执行编组的 Python 版本相同,因为不能保证向后兼容性。
shelve
使用大型数据集变得非常慢。我一直在非常成功地使用redis ,并围绕它编写了一个FreqDist 包装器。它非常快,并且可以同时访问。
您可以使用搁板将数据存储在磁盘上,而不是将整个数据加载到内存中。所以启动时间会非常快,但代价是访问时间会变慢。
Shelve 也会腌制 dict 值,但不会在启动时对所有项目进行(取消)腌制,而只会在每个项目本身的访问时进行。
一些有助于加快进口的事情:
话虽如此,我同意您在第一次导入模块后不应该遇到任何延迟。以下是其他一些一般性想法:
话虽如此,在没有更多背景信息的情况下给你任何具体建议有点困难。更具体地说,您在哪里导入它?计算是什么?
将计算密集型部分分解为单独的模块。然后至少在重新加载时,您不必等待。
尝试使用协议 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.
我正在经历同样的问题......搁置,数据库等......对于这类问题来说都太慢了。您需要接受一次打击,将其插入到 Redis 等内存键/值存储中。它只会存在于内存中(警告它可能会占用大量内存,因此您可能需要一个专用的盒子)。您永远不必重新加载它,您只需在内存中查找密钥
r = Redis()
r.set(key, word)
word = r.get(key)
扩展延迟计算的想法,为什么不把 dict 变成一个根据需要提供(和缓存)元素的类呢?
您还可以使用 psyco 来加快整体执行速度……
或者您可以只使用数据库来存储值?查看 SQLObject,这使得将内容存储到数据库变得非常容易。
对于这个问题,还有另一个非常明显的解决方案。重新加载代码时,原始范围仍然可用。
所以......做这样的事情将确保这段代码只执行一次。
try:
FD
except NameError:
FD = FreqDist(word for word in brown.words())