Times for them creating a million (None, None)
pairs:
23.17 ± 0.83 ms zip_
33.27 ± 0.63 ms pairwise_
Python: 3.11.4 (main, Sep 9 2023, 15:09:21) [GCC 13.2.1 20230801]
The tested things:
def it():
return repeat(None, 10**6)
def pairwise_():
return pairwise(it())
def zip_():
return zip(it(), it())
Why is zip
so much faster? For each pair it has to get elements from two input iterators, whereas pairwise
reuses one element. The latter should be faster.
Comparing zip_next
and pairwise_next
, the only potential reason I see is the latter’s PyTuple_Pack(2, old, new)
. Is that it? Is PyTuple_Pack
harmfully slow? Should it maybe be PyTuple_Pack(old, new)
, i.e., use a version for exactly two elements?
Benchmark script
from timeit import timeit
from statistics import mean, stdev
from collections import deque
from itertools import pairwise, repeat
import sys
def it():
return repeat(None, 10**6)
def pairwise_():
return pairwise(it())
def zip_():
return zip(it(), it())
funcs = pairwise_, zip_
consume = deque(maxlen=1).extend
times = {f: [] for f in funcs}
def stats(f):
ts = [t * 1e3 for t in sorted(times[f])[:5]]
return f'{mean(ts):6.2f} ± {stdev(ts):4.2f} ms '
for _ in range(25):
for f in funcs:
t = timeit(lambda: consume(f()), number=1)
times[f].append(t)
for f in sorted(funcs, key=stats):
print(stats(f), f.__name__)
print('\nPython:', sys.version)