创建大型查找表,性能问题

0 投票
2 回答
2850 浏览
提问于 2025-04-19 23:36

我想创建一个查找表,为此我在考虑使用字典。这个字典的键对应一个整数(或者在我的情况下是来自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 个回答

1

在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()
1

在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]()

当然,你可能想把计算函数中的逻辑提取出来:使用装饰器可能是个不错的选择。

撰写回答