【问题标题】:Python: Why if-statement is faster than addressing a dictionaryPython:为什么 if 语句比寻址字典更快
【发布时间】:2019-07-22 07:31:01
【问题描述】:

我已经阅读了 Stackoverflow 中的几个线程。许多人声称使用 dict 比 Python 中的 if-elif-else 语句更快。我使用 Python 3.7.4 做了一些测试,得到了不同的结果。

我已经构建了一个测试脚本。我使用 timeit 来测量我的代码。我正在构建一个伪代码解释器。我的解释器使用 if-elif-else 语句来查找 python 过程来解释特定的伪代码。但我想转为 dict 批准并进行一些测试。

from timeit import Timer


def add(x, y):
    return x + y


def sub(x, y):
    return x - y


def mul(x, y):
    return x * y


def div(x, y):
    return x / y


def foo1(oper, a, b):
    return funcs[oper](a, b)


def foo2(oper, a, b):
    if oper == 'add':
        return a + b
    elif oper == 'sub':
        return a - b
    elif oper == 'mul':
        return a * b
    else:
        return a / b


def foo3(oper, a, b):
    subfuncs[oper](a, b)


funcs = {'add': lambda x, y: x + y,
         'sub': lambda x, y: x - y,
         'mul': lambda x, y: x * y,
         'div': lambda x, y: x / y}

subfuncs = {'add': add,
            'sub': sub,
            'mul': mul,
            'div': div}

times_to_run = 10000000
t1 = Timer("foo1('div', 3, 2)", "from __main__ import foo1")
t2 = Timer("foo2('div', 3, 2)", "from __main__ import foo2")
t3 = Timer("foo3('div', 3, 2)", "from __main__ import foo3")
tot1 = t1.timeit(times_to_run)
tot2 = t2.timeit(times_to_run)
tot3 = t3.timeit(times_to_run)
print("Time for foo1 is: {:4.2f}".format(tot1))
print("Time for foo2 is: {:4.2f}".format(tot2))
print("Time for foo3 is: {:4.2f}".format(tot3))

if tot1 > tot2:
    res1 = 'slower'
    times1 = '{:6.2f}'.format((tot1 / tot2 - 1) * 100)
elif tot1 < tot2:
    res1 = 'faster'
    times1 = '{:6.2f}'.format((tot2 / tot1 - 1) * 100)
else:
    res1 = 'equal'
    times1 = ''
print("\nfoo1 is {}% {} in comparison to foo2".format(times1, res1))

if tot2 > tot3:
    res2 = 'slower'
    times2 = '{:6.2f}'.format((tot2 / tot3 - 1) * 100)
elif tot2 < tot3:
    res2 = 'faster'
    times2 = '{:6.2f}'.format((tot3 / tot2 - 1) * 100)
else:
    res2 = 'equal'
    times2 = ''
print("foo2 is {}% {} in comparison to foo3".format(times2, res2))

if tot1 > tot3:
    res3 = 'slower'
    times3 = '{:6.2f}'.format((tot1 / tot3 - 1) * 100)
elif tot1 < tot3:
    res3 = 'faster'
    times3 = '{:6.2f}'.format((tot3 / tot1 - 1) * 100)
else:
    res3 = 'equal'
    times3 = ''
print("foo1 is {}% {} in comparison to foo3".format(times3, res3))

我原以为 foo3 会是最快的函数,而 foo2 每次都是最快的。我总是得到与此类似的输出,并且输出始终与此输出一致:

Time for foo1 is: 3.35
Time for foo2 is: 2.99
Time for foo3 is: 3.06

foo1 is  12.18% slower in comparison to foo2
foo2 is   2.51% faster in comparison to foo3
foo1 is   9.44% slower in comparison to foo3

我的问题是为什么具有 if-elif-else 语句的 foo2 比使用函数字典的 foo3 更快?

附言。这不是我的实际代码。我正在测试哪种方法会更快。

【问题讨论】:

  • 你使用什么 CPU/Python?在我的 AMD 2400G/Python 3.6.8 上 foo1 最快 Time for foo1 is: 1.50 Time for foo2 is: 1.52 Time for foo3 is: 1.59
  • 我有一台 Xeon E5-2680 @2.70 GHz,Python 3.7.4 上的 16GB RAM

标签: python-3.x function dictionary if-statement lambda


【解决方案1】:

我的问题是为什么具有 if-elif-else 语句的 foo2 比使用函数字典的 foo3 更快?

好吧,在 foo3 中,您有一个全局名称查找(您的函数使用全局 dict),一个 dict 查找(=> 一个 __getitem__ 方法的属性查找 + 一个对 __getitem__ 的方法调用),还有一个函数调用。

在 foo2 中,你没有全局查找,没有字典查找,也没有函数调用,因为所有操作都是内联的,所以这在很大程度上弥补了顺序丢失测试条件的时间(这就是字典查找的原因 可以,有时比分支更快),特别是当你只有 4 个条件时......

如果您希望您的测试具有相关性,您至少应该使用函数调用重写foo2

def foo2(oper, a, b):
    if oper == 'add':
        return add(a, b)
    elif oper == 'sub':
        return sub(a, b)
    elif oper == 'mul':
        return mul(a, b)
    elif oper == 'div':
        return div(a, b)
    else:
        raise ValueError("unknown operation '{}'".format(oper))

(当然,请确保您并不总是使用“添加”来测试它 - 但在这里您的测试大部分都可以,因为它使用“div”,这是最后一个条件,因此理论上是最坏的情况)。

许多人声称使用 dict 比 Python 中的 if-elif-else 语句更快

如果您阅读相关问题(和答案)like this one,您会发现它实际上不仅仅取决于分支与 dict 查找(有多少分支/dict 键、您的代码有效执行的操作等等等等) )。

【讨论】:

  • 谢谢,这个解释的很好。我不是在比较苹果和苹果。根据您的建议,我认为 foo3 是最快的,而 foo2 是最慢的,而 foo1 是 foo3 的紧密竞争对手。 Time for foo1 is: 3.13 Time for foo2 is: 4.46 Time for foo3 is: 3.03
  • 正确的基准测试确实不是微不足道的,即使对于看似简单的情况,也很容易忽略导致结果偏差的一件简单的事情......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 2017-07-25
  • 2013-12-02
相关资源
最近更新 更多