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

2024-06-18 11:47:29 发布

您现在位置:Python中文网/ 问答频道 /正文

在我的项目中,我必须检查一个值是否是两个值之一。由于我可以使用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)

其中印刷:

Test1 mean: 0.47915725767612455 
Test2 mean: 0.46851890563964843

T-test p-score: 0.001033983121482868

p值表示inor语句循环的执行时间之间的差异是显著的

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

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


Tags: orinimportfordatetimeiftimerange
1条回答
网友
1楼 · 发布于 2024-06-18 11:47:29

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

让我们暂时忘记谁是“赢家”

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

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

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

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


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

让我们研究一下(使用IPython %timeit魔术计时)对于不同数量的短路:

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解决方案与任何给定容器上的in一样快,但一般来说,使用set()是一个更好的选择,因为它证明了自己的快速性(因为它有O(1)查找时间,而没有tuplelist查找时间)与短路赌注无关


最后,为了了解or速度较慢的原因,让我们使用dis分解if_or()if_in_set()

  • if_or()
import dis


dis.dis(if_or)
  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
  • if_in_set()
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

您可以看到,包含多个相对昂贵的COMPARE_OP调用的冗长的第三个if_or()块被单个COMPARE_OP调用所取代。 Python的优化机制正在冻结容器

相关问题 更多 >