21

我发现一些链接谈论在 c++ 中的 switch case 比 if else 更快,因为它可以在编译中进行优化。然后我发现了一些人们认为使用字典可能比使用 If 语句更快的建议。然而,大部分对话都是关于某人的工作,最终只是讨论他们应该首先优化代码的其他部分,除非你做了数百万个 if else,否则这无关紧要。谁能解释这是为什么?

假设我有 100 个唯一的数字,它们将不断地流入 Python 代码。我想检查它是哪个数字,然后执行一些操作。所以我可以做大量的 if else,或者我可以把每个数字放在字典里。为了争论起见,可以说它是一个单线程。

有人了解 python 和低级执行之间的层,可以解释这是如何工作的吗?

谢谢 :)

4

2 回答 2

22

然而,大部分对话都是关于某人的工作,最终只是讨论他们应该首先优化代码的其他部分,除非你做了数百万个 if else,否则这无关紧要。谁能解释这是为什么?

通常,只有在确实需要时才应该费心优化代码,即,如果程序的性能慢得无法使用。

如果是这种情况,您应该使用分析器来确定哪些部分实际上导致了最多的问题。对于 Python,cProfile模块非常适合这一点。

有人了解 python 和低级执行之间的层,可以解释这是如何工作的吗?

如果您想了解代码的执行方式,请查看dis模块。

一个简单的例子......

import dis

# Here are the things we might want to do
def do_something_a():
    print 'I did a'


def do_something_b():
    print 'I did b'


def do_something_c():
    print 'I did c'


# Case 1
def f1(x):
    if x == 1:
        do_something_a()
    elif x == 2:
        do_something_b()
    elif x == 3:
        do_something_c()


# Case 2
FUNC_MAP = {1: do_something_a, 2: do_something_b, 3: do_something_c}
def f2(x):
    FUNC_MAP[x]()


# Show how the functions execute
print 'Case 1'
dis.dis(f1)
print '\n\nCase 2'
dis.dis(f2)

...输出...

Case 1
 18           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       22

 19          12 LOAD_GLOBAL              0 (do_something_a)
             15 CALL_FUNCTION            0
             18 POP_TOP
             19 JUMP_FORWARD            44 (to 66)

 20     >>   22 LOAD_FAST                0 (x)
             25 LOAD_CONST               2 (2)
             28 COMPARE_OP               2 (==)
             31 POP_JUMP_IF_FALSE       44

 21          34 LOAD_GLOBAL              1 (do_something_b)
             37 CALL_FUNCTION            0
             40 POP_TOP
             41 JUMP_FORWARD            22 (to 66)

 22     >>   44 LOAD_FAST                0 (x)
             47 LOAD_CONST               3 (3)
             50 COMPARE_OP               2 (==)
             53 POP_JUMP_IF_FALSE       66

 23          56 LOAD_GLOBAL              2 (do_something_c)
             59 CALL_FUNCTION            0
             62 POP_TOP
             63 JUMP_FORWARD             0 (to 66)
        >>   66 LOAD_CONST               0 (None)
             69 RETURN_VALUE


Case 2
 29           0 LOAD_GLOBAL              0 (FUNC_MAP)
              3 LOAD_FAST                0 (x)
              6 BINARY_SUBSCR
              7 CALL_FUNCTION            0
             10 POP_TOP
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE

...因此很容易看出哪个函数必须执行最多的指令。

至于哪个实际上更快,这是您必须通过分析代码来检查的东西。

于 2013-04-10T11:53:36.747 回答
8

if/elif/else 结构将其赋予的键与一系列可能的值逐一进行比较,直到在某些 if 语句的条件中找到匹配项,然后从 if 块中读取它应该执行的内容。这可能需要很长时间,因为每次查找都必须进行如此多的检查(n/2平均而言,可能的值)。n

if 语句序列比 switch 语句更难优化的原因是条件检查(C++ 中括号内的内容)可能会改变下一次检查中涉及的某个变量的状态,所以你必须这样做他们按顺序。switch 语句的限制消除了这种可能性,因此顺序无关紧要(我认为)。


Python 字典被实现为哈希表。这个想法是这样的:如果您可以处理任意大的数字并拥有无限的 RAM,您可以创建一个巨大的函数指针数组,只需将您的查找值转换为整数并将其用作索引即可。查找几乎是即时的。

当然,您不能这样做,但是您可以创建一个长度可管理的数组,将查找值传递给哈希函数(根据查找值生成一些整数),然后%将结果与您的长度数组以获取该数组范围内的索引。这样,查找所需的时间与调用哈希函数一次、取模并跳转到索引所需的时间一样多。如果不同的可能查找值的数量足够大,那么与那些n/2条件检查相比,散列函数的开销可以忽略不计。

(实际上,由于许多不同的查找值将不可避免地映射到同一个索引,所以并不是那么简单。您必须检查并解决可能的冲突,这可以通过多种方式完成。不过,它的要点是如上所述。)

于 2013-04-10T11:14:35.050 回答