【问题标题】:Why is using a lookup table slower than computing it in this case?为什么在这种情况下使用查找表比计算它慢?
【发布时间】:2020-01-01 18:06:45
【问题描述】:

我有两个函数,其中输入 delta_E 只能是 3 个可能的值,0、4、8。我的第一个函数直接使用 numpy 的指数函数计算它。

def acceptance_prob(delta_E, beta):
    return np.exp(-delta_E*beta)

而我的第二个函数创建了一个字典,预先计算了 3 个不同的值:

def acceptance_prob2(delta_E, beta):
    prob = {i: np.exp(-i*beta) for i in range(0,9,4)}
    return prob[delta_E]

使用魔术命令 %timeit 对两者进行基准测试返回

%timeit -n 1000000 acceptance_prob(4,5)
3.02 µs ± 13.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit -n 1000000 acceptance_prob2(4,5)
10.9 µs ± 37.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

显示预计算方法比使用 np.exp() 慢 3 倍多一点,我不明白。据我所知,计算机语言通常使用泰勒级数展开来计算指数函数,所以肯定预先计算它们并查找它们而不是计算它们每个循环更快?

【问题讨论】:

  • acceptance_prob2 计算三个值,另一个只计算一次。如果你在函数之外make prob 那么函数应该更快。
  • 那么我需要在我的函数之外预先计算它吗?你是说每次我使用acceptance_prob2 计算3次?我不是按照我的问题所说的那样做吗,哈哈
  • Are you saying that each time I use acceptance_prob2 it calculates 3 times? -
  • 如果您一次只评估一个标量值,请使用math.expnp.exp 应该用于数组输入 - 许多值,例如 np.exp(-delta_E, np.arange(0,100))。这种计算将比重复的字典查找快很多。 numpy 中加快速度的关键是使用编译后的数值方法,而不是 Python 级别的迭代。

标签: python performance numpy benchmarking


【解决方案1】:

试试这个:

def acceptance_prob(delta_E, beta):
    return np.exp(-delta_E*beta)

PRECOMPUTED_BETA = 5
PRECOMPUTED_PROB = {i: np.exp(-i*PRECOMPUTED_BETA) for i in range(0,9,4)}
def acceptance_prob2(delta_E, beta):
    if beta==PRECOMPUTED_BETA:
        return PRECOMPUTED_PROB[delta_E]
    return acceptance_prob(delta_E, beta)

使用魔术命令%timeit 对两者进行基准测试返回:

%timeit -n 1000000 acceptance_prob(4,5)
1.09 µs ± 9.66 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit -n 1000000 acceptance_prob2(4,5)
107 ns ± 0.322 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

但这是一个更通用的解决方案:

In [15]: class Memoize:
    ...:     def __init__(self, f):
    ...:         self.f = f
    ...:         self.memo = {}
    ...:     def __call__(self, *args):
    ...:         if not args in self.memo:
    ...:             self.memo[args] = self.f(*args)
    ...:         #Warning: You may wish to do a deepcopy here if returning objects
    ...:         return self.memo[args]
    ...:

In [16]: @Memoize
    ...: def acceptance_prob3(delta_E, beta):
    ...:     return np.exp(-delta_E*beta)
    ...:

In [17]: %timeit -n 1000000 acceptance_prob3(4,5)
245 ns ± 1.19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [18]:

【讨论】:

  • 我认为您的回答可能有点矫枉过正。愚蠢的我没有意识到每次运行该函数时我实际上都在定义我的字典。也只是想知道你的类函数在做什么?
  • 延迟评估和记忆通常比预计算更有效。
  • @vy32 在这种只有 3 个预计算值的情况下,使用这两种方法的速度会不会有任何显着差异?这个函数实际上是 Ising 模型的接受概率,它在格子总数N^2 上循环,然后在平衡步数EQ_STEPS 上再次循环。我已经解决了我的问题,我基本上将我的预计算移出这些循环,这样这些值实际上只被评估一次。但是我很好奇在这种情况下惰性评估和记忆是否会做得更好?
  • 如果您只打算调用具有三个可能值的函数,并且要执行数十亿次,那么您甚至不应该调用函数。只需将它们存储在一个数组中,这比字典查找更快。但是你为什么一开始要优化这么多呢?
【解决方案2】:

我认为您并没有按照您的想法测试时间复杂度。第二个功能不仅仅是查找值。它预先计算三个值,然后查找其中一个。如果您在函数之前预先计算值,然后将它们传递给函数,您将更直接地测试查找时间。

类似:

def acceptance_prob2(delta_E, prob):
    return prob[delta_E]
prob = {i: np.exp(-i*beta) for i in range(0,9,4)}
%timeit -n 1000000 acceptance_prob2(4, prob)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    • 1970-01-01
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多