为什么在CPython中,`len(l) != 0`比`bool(l)`更快?

9

我进行了一些有关列表操作速度的实验。为此,我定义了两个列表:l_short = []l_long = list(range(10**7))

我的想法是比较 bool(l)len(l) != 0 的性能差异。

在一个包含if的测试中,使用以下实现比if len(l) != 0: pass要快得多: if l: pass

但是如果不含有if测试,我得到以下结果:

%%timeit
len(l_long) != 0
# 59.8 ns ± 0.358 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
bool(l_long)
# 63.3 ns ± 0.192 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

为什么使用 bool 的时间略长?

以下是使用 dis 模块显示的字节码(供参考)

dis("len(l_long) != 0")
"""
  1           0 LOAD_NAME                0 (len)
              2 LOAD_NAME                1 (l_long)
              4 CALL_FUNCTION            1
              6 LOAD_CONST               0 (0)
              8 COMPARE_OP               3 (!=)
             10 RETURN_VALUE
"""

dis("bool(l_long)")
"""
  1           0 LOAD_NAME                0 (bool)
              2 LOAD_NAME                1 (l_long)
              4 CALL_FUNCTION            1
              6 RETURN_VALUE
"""

布尔类型和真值运算符之间有什么区别? - Chris_Rands
1个回答

7

bool(l_long) 首先尝试调用 l_long.__bool_() 方法,但是,list.__bool__ 未定义。接下来的步骤是调用 l_long.__len__() != 0

另一方面,len(l_long) != 0 直接进入 l_long.__len__()

你看到的时间差实际上就是捕获 l_long.__bool__ 抛出的 AttributeError 异常所需的时间,然后再调用 l_long.__len__


非常感谢您的回答。顺便说一下,我认为 if l_long: pass 隐式地调用了 bool(l),但显然我是错的。在 if l_long: pass 中会发生什么? - BlueSheepToken
我认为bool()慢的原因还有其他,可以在这里的评论中阅读https://dev59.com/HKnka4cB1Zd3GeqPKz2f#nunpnYgBc1ULPQZFqeOu - Chris_Rands
如果您查看if l_long:pass的字节码,您将看到它直接跳转到POP_JUMP_IF_FALSE,而在字节码级别不调用任何函数。 - chepner
@chepner,我注意到了,那么这是否意味着解释器会在背后为我们处理一些优化? - BlueSheepToken
1
基本上说,当你写bool(l_long)时,解析器只能看到你调用了一个绑定到名称bool的东西;因此,字节码生成器不能假设bool仍然绑定到内置类型bool(至少不需要比你希望解析器做更多的工作),因此必须像其他任何函数调用一样对待它。另一方面,使用if l_long时,在运行时没有任何可以覆盖的内容:在布尔上下文中使用l_long,因此可以直接跳转到解释器中隐藏的好东西。 - chepner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接