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)