Compare these two functions:
def short():
try:
if 0 == 1:
unreached
raise RuntimeError
except RuntimeError:
pass
def long():
try:
if 0 == 1:
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
raise RuntimeError
except RuntimeError:
pass
The only difference is that long() has 100 unreached statements instead of just one. But it takes much longer:
357.0 ± 0.3 ns short
1355.8 ± 1.5 ns long
Python: 3.11.4 (main, Jun 24 2023, 10:18:04) [GCC 13.1.1 20230429]
And when I increase it to 200 unreached statements, it takes even longer:
358.3 ± 1.3 ns short
2357.8 ± 2.9 ns long
Python: 3.11.4 (main, Jun 24 2023, 10:18:04) [GCC 13.1.1 20230429]
So it looks like it takes an extra 10 ns for each unreached statement. Why? Shouldn’t it just “skip” them all in constant time and be just as fast as short()?
Benchmark code (Attempt This Online!):
from timeit import timeit
from time import perf_counter as time
from statistics import mean, stdev
import sys
def short():
try:
if 0 == 1:
unreached
raise RuntimeError
except RuntimeError:
pass
def long():
try:
if 0 == 1:
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached; unreached
raise RuntimeError
except RuntimeError:
pass
funcs = short, long
for _ in range(3):
times = {f: [] for f in funcs}
def stats(f):
ts = [t * 1e9 for t in sorted(times[f])[:5]]
return f'{mean(ts):6.1f} ± {stdev(ts):4.1f} ns '
for _ in range(100):
for f in funcs:
t = timeit(f, number=10**4) / 1e4
times[f].append(t)
for f in sorted(funcs, key=stats):
print(stats(f), f.__name__)
print()
print('Python:', sys.version)