创建大型查找表,性能问题
我想创建一个查找表,为此我在考虑使用字典。这个字典的键对应一个整数(或者在我的情况下是来自Enum
类的枚举类型),而值则是2、3或4个numpy数组。但我对这种方法有些犹豫,因为这个字典包含的信息量非常大,而其中99%的信息在某些问题中可能根本用不上。所以,构建一个包含所有查找信息的单一对象似乎没有意义。虽然我只是猜测,但我几乎可以肯定,有更好的方法可以实现我想做的事情。
在C++的世界里,我会创建一个unordered_map
,将枚举类型映射到函数指针,在这个函数里我会创建一个static
数组(这样只会创建一次),然后返回这个数组的指针。这样,我就只会实例化程序真正需要的查找表的一部分,而不是整个表。
但我想在Python中做类似的事情,所以我想知道实现这个目标的最有效方法是什么。
编辑
这是我到目前为止想到的。我有点混合了@AaronDigulla和@DanielRoseman的建议,尽管@runonce
可能不再必要了。一个字典的子类重写了__getitem__
方法,并检查字典中是否存在某个键。如果不存在,它会调用一个函数(使用eval()
对字典键值的拼接字符串进行处理)。我希望能对给出的代码提出任何改进。看起来有点复杂,但它能工作,所以我在想是否可以进一步简化。
import collections, types
import numpy as np
Quadrature = collections.namedtuple("Quadrature", "wgt xi eta zeta")
category_map = { "t3" : "tri" #... more types
}
class Integrator(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def __getitem__(self, key):
if not key in self:
fn = '{}_{}gp'.format(category_map[key[0]], str(key[1]))
super().__setitem__(key, eval(fn)())
val = super().__getitem__(key)
return val
def __repr__(self):
dictrepr = dict.__repr__(self)
return '%s(%s)' % (type(self).__name__, dictrepr)
def update(self, *args, **kwargs):
print ('update', args, kwargs)
for k, v in dict(*args, **kwargs).items():
self[k] = v
def run_once(f):
def wrapper(*args, **kwargs):
if not wrapper.has_run:
wrapper.has_run = True
return f(*args, **kwargs)
wrapper.has_run = False
return wrapper
@run_once
def tri_3gp():
xi = np.array([2/3., 1/6., 1/6.])
eta = np.array([1/6., 1/6., 2/3.])
wgt = np.array([2/3., 2/3., 2/3.]);
return Quadrature(wgt, xi, eta, None)
2 个回答
在Python中,这个操作非常简单。你可以查看这个问题,了解如何创建“只运行一次”的装饰器:在循环中让一个函数只执行一次的有效方法
现在你可以把生成数据的函数放到一个映射中。这个装饰器会确保这些函数最多只运行一次(也就是你第一次调用它们的时候)。然后查找的方式就像这样:
@run_once
def funcForKey():
...
lookup_dict = {
'key': funcForKey,
...
}
result = lookup_dict[x]()
在[]
后面的()
是用来调用这个函数的。
你也可以尝试使用一个类:
class Data(object):
@run_once
def key(self):
...
data = Data()
现在你可以这样查找值:
a = 'key'
result = getattr(data, a)()
或者,如果名字在运行时是固定的,可以简单地:
result = data.key()
在Python中,你可以做完全一样的事情。实际上,这甚至更简单,因为函数本身就是一等公民:你可以把它们存放在字典里,随时调用。
为了替代静态数组,你可以使用某种记忆化的方法,比如一个标准的全局查找数组。
global_dict = {}
def func1():
if 'param1' not in global_dict:
global_dict['param1'] = my_complicated_function_for_param_1()
return global_dict['param1'] = my_complicated_function_for_param_1()
lookup_dict = {
'param1': func1,
...
}
# now do the lookup:
my_result = lookup_dict[my_param]()
当然,你可能想把计算函数中的逻辑提取出来:使用装饰器可能是个不错的选择。