9

I was doing some experimentation about operations' speed on list. For this I defined two list: l_short = [] and l_long = list(range(10**7)).

The idea is to compare bool(l) with len(l) != 0

In an if contest, the following implementation is faster by a lot if l: pass instead of if len(l) != 0: pass

But without the if contest I got the following results:

%%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)

The timing of bool is slightly longer, why so ?

Here are the bytecodes using dis (FYI)

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
"""
BlueSheepToken
  • 5,751
  • 3
  • 17
  • 42

1 Answers1

7

bool(l_long) first tries to call l_long.__bool_(); however, list.__bool__ isn't defined. The next step is to call l_long.__len__() != 0.

len(l_long) != 0, on the other hand, goes straight to l_long.__len__()

The time difference you see is essentially the time it takes to catch the AttributeError raised by l_long.__bool__ before calling l_long.__len__ anyway.

L3viathan
  • 26,748
  • 2
  • 58
  • 81
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thank you very mich for this answer. As a side note, I thought `if l_long: pass` implicitly called `bool(l)`, I am obviously wrong. What does happen in `if l_long: pass` ? – BlueSheepToken Aug 08 '19 at 13:09
  • I think there are other reasons why `bool()` is slow too, read the comments here https://stackoverflow.com/questions/48909056/creating-a-list-within-a-list-in-python/#comment84833686_48909138 – Chris_Rands Aug 08 '19 at 13:09
  • If you look at the byte code for `if l_long: pass`, you'll see it jumps right to `POP_JUMP_IF_FALSE` without calling any function at the byte-code level. – chepner Aug 08 '19 at 13:11
  • @chepner, I noticed that, so does that mean the interpreter takes care for us of some optimizations underneath ? – BlueSheepToken Aug 08 '19 at 13:15
  • 1
    Essentially. When you write `bool(l_long)`, the parser only sees that you are calling something bound to the name `bool`; as a result, the byte-code generator can't assume that `bool` is still bound to the built-in type `bool` (at least not without more work than you want the parser doing), and therefore has to treat it like any other function call. With `if l_long`, on the other hand, there's nothing that could have been overriden at run-time: `l_long` is used in a boolean context, period, so you can skip right to the good stuff buried in the interpreter. – chepner Aug 08 '19 at 13:22