Why is `if x is None: pass` faster than `x is None` alone?

154 Views Asked by At

Timing results in Python 3.12 (and similar with 3.11 and 3.13 on different machines):

When x = None:
13.8 ns  x is None
10.1 ns  if x is None: pass

When x = True:
13.9 ns  x is None
11.1 ns  if x is None: pass

How can doing more take less time?

Why is if x is None: pass faster, when it does the same x is None check and then additionally checks the truth value of the result (and does or skips the pass)?

Times on other versions/machines:

  • Python 3.11: (12.4, 9.3) and (12.0, 8.8)
  • Python 3.13: (12.7, 9.9) and (12.7, 9.6)

Benchmark script (Attempt This Online!):

from timeit import repeat
import sys

for x in None, True:
    print(f'When {x = }:')
    for code in ['x is None', 'if x is None: pass'] * 2:
        t = min(repeat(code, f'{x=}', repeat=100))
        print(f'{t*1e3:4.1f} ns ', code)
    print()

print('Python:', sys.version)
2

There are 2 best solutions below

0
deceze On BEST ANSWER

Look at the disassembled code:

>>> import dis
>>> dis.dis('if x is None: pass')
  0           0 RESUME                   0

  1           2 LOAD_NAME                0 (x)
              4 POP_JUMP_IF_NOT_NONE     1 (to 8)
              6 RETURN_CONST             0 (None)
        >>    8 RETURN_CONST             0 (None)
>>> dis.dis('x is None')
  0           0 RESUME                   0

  1           2 LOAD_NAME                0 (x)
              4 LOAD_CONST               0 (None)
              6 IS_OP                    0
              8 RETURN_VALUE

The if case has a special POP_JUMP_IF_NOT_NONE operation, which is faster than a LOAD_CONST plus IS_OP. You can read the detailed discussion about it here: https://github.com/faster-cpython/ideas/discussions/154.

0
no comment On

To augment what @deceze showed, here's what happens when we don't use None but a variable none set to None. Then Python doesn't compile that to the special POP_JUMP_IF_NOT_NONE but as expected to the same LOAD_NAME+LOAD_NAME+IS_OP as for x is none, plus a POP_JUMP_IF_FALSE that indeed does a truth check of the IS_OP result and then jumps or not. And as expected, that does take longer (Attempt This Online!):

When x = None:
13.6 ns  x is none
15.0 ns  if x is none: pass

When x = True:
13.6 ns  x is none
14.5 ns  if x is none: pass

Bytecode for x is none:

  1           2 LOAD_NAME                0 (x)
              4 LOAD_NAME                1 (none)
              6 IS_OP                    0
              8 RETURN_VALUE

Bytecode for if x is none: pass:

  1           2 LOAD_NAME                0 (x)
              4 LOAD_NAME                1 (none)
              6 IS_OP                    0
              8 POP_JUMP_IF_FALSE        1 (to 12)
             10 RETURN_CONST             0 (None)
        >>   12 RETURN_CONST             0 (None)