I’m doing some microopitimization for textual (so it plays nicer for mypy mainly but it needs to microbench as good or better than before)
and I noticed using:
while True:
if x is None:
break
...
is better than
while x is not None:
...
am I doing the benchmark correctly?
I posted this on the Python Discord and @Jelle recommended reporting this as an issue to the cpython tracker, I’m not sure if this is necessary as I’m getting a high standard deviation in my benchmarks so I’d like some more advice on fixing that first.
Another approach I tried is inverting the trick so I’m using x is not None like I would in the while loop, and that’s slower than the idiomatic option:
while True:
if node is not None:
nodes.append(node)
node = node._parent
else:
return nodes
I think I noticed that difference as well a while ago, but concluded that there’s no meaningful reason that the uglier code is faster, that it’s just s quirk of the current implementation of the interpreter and might change any day. And the difference was tiny. So I went with the prettier code.
Honestly, those timings are all within a standard deviation of each other. I don’t know if you’re consistently able to reproduce it (or what versions you’re able to reproduce it on), but I suspect you’re really just measuring the cost of resizing a list a ton of times, not necessarily the cost of branching-vs-not-branching.
n = 10**3
root = None
for _ in range(n):
root = root,
roots = [root] * 100
def while_True():
for x in roots:
while True:
if x is None:
break
x = x[0]
def while_condition():
for x in roots:
while x is not None:
x = x[0]
funcs = [while_True, while_condition]
from timeit import timeit
from statistics import mean, stdev
import sys
import random
for i in range(6):
times = {f: [] for f in funcs}
def stats(f):
ts = [t * 1e9 for t in sorted(times[f])[:10]]
return f'{mean(ts):5.2f} ± {stdev(ts):4.2f} ns '
for _ in range(1000):
random.shuffle(funcs)
for f in funcs:
t = timeit(f, number=1) / 1 / len(roots) / n
times[f].append(t)
if i < 3:
continue
for f in sorted(funcs, key=stats):
print(stats(f), f.__name__)
print()
print('\nPython:', sys.version)