Python 为什么or语句比in语句快?

Python 为什么or语句比in语句快?,python,performance,if-statement,Python,Performance,If Statement,在我的项目中,我必须检查一个值是否是两个值之一。由于我可以使用if or语句或if in语句来实现这一点,而且我不知道这两个语句中哪一个运行得更快,因此我运行了以下代码来检查它们各自的性能: import time import datetime from scipy.stats import ttest_ind def if_in(t): bln = False for z in range(400): if z % 100 == 0: print("tes

在我的项目中,我必须检查一个值是否是两个值之一。由于我可以使用if or语句或if in语句来实现这一点,而且我不知道这两个语句中哪一个运行得更快,因此我运行了以下代码来检查它们各自的性能:

import time
import datetime
from scipy.stats import ttest_ind


def if_in(t):
    bln = False
    for z in range(400):
        if z % 100 == 0: print("test1", z)
        starttime = time.time()
        for x in range(1000000):
            for i in range(5):
                if i in [2, 4]:
                    bln = True
        t.append(time.time() - starttime)
    return t


def if_or(t):
    bln = False
    for z in range(400):
        if z % 100 == 0: print("test2", z)
        starttime = time.time()
        for x in range(1000000):
            for i in range(5):
                if i == 2 or i == 4:
                    bln = True
        t.append(time.time() - starttime)
    return t


st = time.time()

times1 = if_in([])
times2 = if_or([])

t, p = ttest_ind(times1, times2)

print("\nTotal execution time:", str(datetime.timedelta(seconds=time.time() - st)))
t1mean = sum(times1) / len(times1)
t2mean = sum(times2) / len(times2)
print("Test1 mean:", t1mean, "\nTest2 mean:", t2mean)
print("\nT-test p-score:", p)
其中印刷:

测试1平均值:0.47915725761612455 测试2平均值:0.46851890563964843 T检验p得分:0.001033983121482868 p值表示in和or语句循环的执行时间之间的差异很大

为什么会有这种差异?对于“or”方法,我假设当第一个条件被认为是真的时,进一步的检查将停止。我再次假设,“in”方法也是如此。但是,其中一个确实比另一个跑得快


此外,这会持续更多的情况吗?例如,何时应将我检查为100个值之一?

您的观察结果存在多个问题

让我们暂时忘记谁是赢家

第一个最重要的问题是,您观察到一些统计上显著的偏离值的平均值,并将其概括为某部分代码执行速度的反映。 虽然这对于特定的代码运行可能是正确的,但是对于一般的方法来说,没有什么可说的,因为在这个级别上,您的度量主要是由操作系统驱动的波动。 我很有信心我自己也观察到了这一点,这段代码的多次运行将为每次运行带来不同的赢家

第二个问题是,您使用的是不适合基准测试的。您可能应该使用,即使这样,它也可能不适合测量如此短的计时

第三个问题是,您的数据不支持您的结论,因为tmean2与if_相关,或者实际上小于tmean1与if_in相关

请注意,这是非常具有挑战性的,可能与实际测量您建议的两个选项之间哪一个更快无关

相反,研究第二个问题是有趣的,即对于模式x==y0或x==y1等的较大重复,在容器中使用是否更快

让我们研究一下,使用IPython%timeit magic对不同短路量的计时:

def if_or(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k == 0 or k == 1 or k == 2 or k == 3 or k == 4 \
                    or k == 5 or k == 6 or k == 7 or k == 8 or k == 9:
                pass


def if_in_set(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}:
                pass


def if_in_tuple(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
                pass


def if_in_list(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
                pass


n = 100000
m = 20
ks = [0] * m
%timeit if_or(n, ks)
# 10 loop, best of 3: 59 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 57.6 ms per loop
%timeit if_in_tuple(n, ks)
# 10 loop, best of 3: 52.4 ms per loop
%timeit if_in_list(n, ks)
# 10 loop, best of 3: 54.7 ms per loop


ks = list(range(m))
%timeit if_or(n, ks)
# 1 loop, best of 3: 351 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 57.6 ms per loop
%timeit if_in_tuple(n, ks)
# 1 loop, best of 3: 209 ms per loop
%timeit if_in_list(n, ks)
# 1 loop, best of 3: 214 ms per loop


ks = [-1] * m
%timeit if_or(n, ks)
# 1 loop, best of 3: 421 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 54.4 ms per loop
%timeit if_in_tuple(n, ks)
# 1 loop, best of 3: 238 ms per loop
%timeit if_in_list(n, ks)
# 1 loop, best of 3: 237 ms per loop
正如您所看到的,在足够短的时间内,or解决方案的速度与在任何给定容器上的速度一样快,但一般来说,使用集合是一个更好的选择,因为它证明了自己的速度很快,因为它有O1查找时间,而元组或列表的查找时间不长,并且与短路赌注无关

最后,为了了解速度较慢的原因或原因,让我们使用dis分解if_或if_in_set:

如果 如果_在_集合中 在这里,您可以看到第三个冗长的if_或带有多个相对昂贵的COMPARE_OP调用的块被一个COMPARE_OP调用所取代。
Python的优化机制正在冻结容器。

您的观察结果存在多个问题

让我们暂时忘记谁是赢家

第一个最重要的问题是,您观察到一些统计上显著的偏离值的平均值,并将其概括为某部分代码执行速度的反映。 虽然这对于特定的代码运行可能是正确的,但是对于一般的方法来说,没有什么可说的,因为在这个级别上,您的度量主要是由操作系统驱动的波动。 我很有信心我自己也观察到了这一点,这段代码的多次运行将为每次运行带来不同的赢家

第二个问题是,您使用的是不适合基准测试的。您可能应该使用,即使这样,它也可能不适合测量如此短的计时

第三个问题是,您的数据不支持您的结论,因为tmean2与if_相关,或者实际上小于tmean1与if_in相关

请注意,这是非常具有挑战性的,可能与实际测量您建议的两个选项之间哪一个更快无关

相反,研究第二个问题是有趣的,即对于模式x==y0或x==y1等的较大重复,在容器中使用是否更快

让我们研究一下,使用IPython%timeit magic对不同短路量的计时:

def if_or(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k == 0 or k == 1 or k == 2 or k == 3 or k == 4 \
                    or k == 5 or k == 6 or k == 7 or k == 8 or k == 9:
                pass


def if_in_set(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}:
                pass


def if_in_tuple(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
                pass


def if_in_list(n, ks, timer=time.perf_counter):
    for _ in range(n):
        for k in ks:
            if k in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
                pass


n = 100000
m = 20
ks = [0] * m
%timeit if_or(n, ks)
# 10 loop, best of 3: 59 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 57.6 ms per loop
%timeit if_in_tuple(n, ks)
# 10 loop, best of 3: 52.4 ms per loop
%timeit if_in_list(n, ks)
# 10 loop, best of 3: 54.7 ms per loop


ks = list(range(m))
%timeit if_or(n, ks)
# 1 loop, best of 3: 351 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 57.6 ms per loop
%timeit if_in_tuple(n, ks)
# 1 loop, best of 3: 209 ms per loop
%timeit if_in_list(n, ks)
# 1 loop, best of 3: 214 ms per loop


ks = [-1] * m
%timeit if_or(n, ks)
# 1 loop, best of 3: 421 ms per loop
%timeit if_in_set(n, ks)
# 10 loop, best of 3: 54.4 ms per loop
%timeit if_in_tuple(n, ks)
# 1 loop, best of 3: 238 ms per loop
%timeit if_in_list(n, ks)
# 1 loop, best of 3: 237 ms per loop
正如您所看到的,在足够短的时间内,or解决方案的速度与在任何给定容器上的速度一样快,但一般来说,使用集合是一个更好的选择,因为它证明了自己的速度很快,因为它有O1查找时间,而元组或列表的查找时间不长,并且与短路赌注无关

最后,为了了解速度较慢的原因或原因,让我们使用dis分解if_或if_in_set:

如果 如果_在_集合中 在那里,你可以看到第三块较长的if_或具有多个相对昂贵的 e COMPARE_OP调用将替换为单个COMPARE_OP调用。
容器被Python的优化机制冻结。

如果_in有400*1000000次构建列表对象的开销,而其他函数没有。我几乎不会调用468 ms比479 ms快得多。同样,这表明if_或比if_in快。当事实上t2mean小于t1mean时,为什么说in语句的执行时间明显少于替代语句?请执行两次相同函数的测试。第二次运行会更快一些,因为已经完成了一些背景魔术初始化。你可以考虑使用一个更好的表示RIN——这样一个小列表-列表是可以的,对于一个更大的列表,估计10个项目中的一个集合对于检查正确性来说要快得多:如果我在FROZSESET { 2, 4 }中:-并将集合的声明放在loopif_in之外,它有400*1000000次构建列表对象的开销,这是另一个函数所没有的。我几乎不会调用468 ms比479 ms快得多。另外,这表明if_或比if_in快。当事实上t2mean小于t1mean时,为什么说in语句的执行时间明显少于替代语句?请执行两次相同函数的测试。第二次运行会更快一些,因为已经完成了一些背景魔术初始化。你可以考虑使用一个更好的表示RIN——这样一个小列表-列表是可以的,对于一个更大的列表,估计10个项目中的一个集合对于检查正确性来说要快得多:如果我在FROZSESET { 2, 4 }中:-并将集合的声明置于循环之外这是一个很好的答案:这两种方法都是对我的方法的关键分解,然后是对这两种方法的深入探索。我不能要求更多!这是一个很好的答案:这两种方法都对我的方法进行了关键性的分解,然后对这两种方法进行了深入的探讨。我不能要求更多!
  2           0 SETUP_LOOP             110 (to 112)
              2 LOAD_GLOBAL              0 (range)
              4 LOAD_FAST                0 (n)
              6 CALL_FUNCTION            1
              8 GET_ITER
        >>   10 FOR_ITER                98 (to 110)
             12 STORE_FAST               3 (_)

  3          14 SETUP_LOOP              92 (to 108)
             16 LOAD_FAST                1 (ks)
             18 GET_ITER
        >>   20 FOR_ITER                84 (to 106)
             22 STORE_FAST               4 (k)

  4          24 LOAD_FAST                4 (k)
             26 LOAD_CONST               1 (0)
             28 COMPARE_OP               2 (==)
             30 POP_JUMP_IF_TRUE        20
             32 LOAD_FAST                4 (k)
             34 LOAD_CONST               2 (1)
             36 COMPARE_OP               2 (==)
             38 POP_JUMP_IF_TRUE        20
             40 LOAD_FAST                4 (k)
             42 LOAD_CONST               3 (2)
             44 COMPARE_OP               2 (==)
             46 POP_JUMP_IF_TRUE        20
             48 LOAD_FAST                4 (k)
             50 LOAD_CONST               4 (3)
             52 COMPARE_OP               2 (==)
             54 POP_JUMP_IF_TRUE        20
             56 LOAD_FAST                4 (k)
             58 LOAD_CONST               5 (4)
             60 COMPARE_OP               2 (==)
             62 POP_JUMP_IF_TRUE        20
             64 LOAD_FAST                4 (k)
             66 LOAD_CONST               6 (5)
             68 COMPARE_OP               2 (==)
             70 POP_JUMP_IF_TRUE        20
             72 LOAD_FAST                4 (k)
             74 LOAD_CONST               7 (6)
             76 COMPARE_OP               2 (==)
             78 POP_JUMP_IF_TRUE        20
             80 LOAD_FAST                4 (k)
             82 LOAD_CONST               8 (7)
             84 COMPARE_OP               2 (==)
             86 POP_JUMP_IF_TRUE        20
             88 LOAD_FAST                4 (k)
             90 LOAD_CONST               9 (8)
             92 COMPARE_OP               2 (==)
             94 POP_JUMP_IF_TRUE        20
             96 LOAD_FAST                4 (k)
             98 LOAD_CONST              10 (9)
            100 COMPARE_OP               2 (==)
            102 POP_JUMP_IF_FALSE       20

  5         104 JUMP_ABSOLUTE           20
        >>  106 POP_BLOCK
        >>  108 JUMP_ABSOLUTE           10
        >>  110 POP_BLOCK
        >>  112 LOAD_CONST               0 (None)
            114 RETURN_VALUE
import dis


dis.dis(if_in_set)
  9           0 SETUP_LOOP              38 (to 40)
              2 LOAD_GLOBAL              0 (range)
              4 LOAD_FAST                0 (n)
              6 CALL_FUNCTION            1
              8 GET_ITER
        >>   10 FOR_ITER                26 (to 38)
             12 STORE_FAST               3 (_)

 10          14 SETUP_LOOP              20 (to 36)
             16 LOAD_FAST                1 (ks)
             18 GET_ITER
        >>   20 FOR_ITER                12 (to 34)
             22 STORE_FAST               4 (k)

 11          24 LOAD_FAST                4 (k)
             26 LOAD_CONST              11 (frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}))
             28 COMPARE_OP               6 (in)
             30 POP_JUMP_IF_FALSE       20

 12          32 JUMP_ABSOLUTE           20
        >>   34 POP_BLOCK
        >>   36 JUMP_ABSOLUTE           10
        >>   38 POP_BLOCK
        >>   40 LOAD_CONST               0 (None)
             42 RETURN_VALUE