Skip to content

Commit f75d59e

Browse files
authored
bpo-35884: Add variable access benchmarking script (GH-11725)
1 parent 2c2ba05 commit f75d59e

File tree

3 files changed

+282
-0
lines changed

3 files changed

+282
-0
lines changed

Doc/whatsnew/3.8.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,3 +551,11 @@ CPython bytecode changes
551551
* Added new opcode :opcode:`END_ASYNC_FOR` for handling exceptions raised
552552
when awaiting a next item in an :keyword:`async for` loop.
553553
(Contributed by Serhiy Storchaka in :issue:`33041`.)
554+
555+
556+
Demos and Tools
557+
---------------
558+
559+
* Added a benchmark script for timing various ways to access variables:
560+
``Tools/scripts/var_access_benchmark.py``.
561+
(Contributed by Raymond Hettinger in :issue:`35884`.)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a benchmark script for timing various ways to access variables:
2+
``Tools/scripts/var_access_benchmark.py``.

Tools/scripts/var_access_benchmark.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
'Show relative speeds of local, nonlocal, global, and built-in access.'
2+
3+
# Please leave this code so that it runs under older versions of
4+
# Python 3 (no f-strings). That will allow benchmarking for
5+
# cross-version comparisons. To run the benchmark on Python 2,
6+
# comment-out the nonlocal reads and writes.
7+
8+
from collections import deque, namedtuple
9+
10+
trials = [None] * 500
11+
steps_per_trial = 25
12+
13+
class A(object):
14+
def m(self):
15+
pass
16+
17+
class B(object):
18+
__slots__ = 'x'
19+
def __init__(self, x):
20+
self.x = x
21+
22+
class C(object):
23+
def __init__(self, x):
24+
self.x = x
25+
26+
def read_local(trials=trials):
27+
v_local = 1
28+
for t in trials:
29+
v_local; v_local; v_local; v_local; v_local
30+
v_local; v_local; v_local; v_local; v_local
31+
v_local; v_local; v_local; v_local; v_local
32+
v_local; v_local; v_local; v_local; v_local
33+
v_local; v_local; v_local; v_local; v_local
34+
35+
def make_nonlocal_reader():
36+
v_nonlocal = 1
37+
def inner(trials=trials):
38+
for t in trials:
39+
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
40+
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
41+
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
42+
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
43+
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
44+
inner.__name__ = 'read_nonlocal'
45+
return inner
46+
47+
read_nonlocal = make_nonlocal_reader()
48+
49+
v_global = 1
50+
def read_global(trials=trials):
51+
for t in trials:
52+
v_global; v_global; v_global; v_global; v_global
53+
v_global; v_global; v_global; v_global; v_global
54+
v_global; v_global; v_global; v_global; v_global
55+
v_global; v_global; v_global; v_global; v_global
56+
v_global; v_global; v_global; v_global; v_global
57+
58+
def read_builtin(trials=trials):
59+
for t in trials:
60+
oct; oct; oct; oct; oct
61+
oct; oct; oct; oct; oct
62+
oct; oct; oct; oct; oct
63+
oct; oct; oct; oct; oct
64+
oct; oct; oct; oct; oct
65+
66+
def read_classvar_from_class(trials=trials, A=A):
67+
A.x = 1
68+
for t in trials:
69+
A.x; A.x; A.x; A.x; A.x
70+
A.x; A.x; A.x; A.x; A.x
71+
A.x; A.x; A.x; A.x; A.x
72+
A.x; A.x; A.x; A.x; A.x
73+
A.x; A.x; A.x; A.x; A.x
74+
75+
def read_classvar_from_instance(trials=trials, A=A):
76+
A.x = 1
77+
a = A()
78+
for t in trials:
79+
a.x; a.x; a.x; a.x; a.x
80+
a.x; a.x; a.x; a.x; a.x
81+
a.x; a.x; a.x; a.x; a.x
82+
a.x; a.x; a.x; a.x; a.x
83+
a.x; a.x; a.x; a.x; a.x
84+
85+
def read_instancevar(trials=trials, a=C(1)):
86+
for t in trials:
87+
a.x; a.x; a.x; a.x; a.x
88+
a.x; a.x; a.x; a.x; a.x
89+
a.x; a.x; a.x; a.x; a.x
90+
a.x; a.x; a.x; a.x; a.x
91+
a.x; a.x; a.x; a.x; a.x
92+
93+
def read_instancevar_slots(trials=trials, a=B(1)):
94+
for t in trials:
95+
a.x; a.x; a.x; a.x; a.x
96+
a.x; a.x; a.x; a.x; a.x
97+
a.x; a.x; a.x; a.x; a.x
98+
a.x; a.x; a.x; a.x; a.x
99+
a.x; a.x; a.x; a.x; a.x
100+
101+
def read_namedtuple(trials=trials, D=namedtuple('D', ['x'])):
102+
a = D(1)
103+
for t in trials:
104+
a.x; a.x; a.x; a.x; a.x
105+
a.x; a.x; a.x; a.x; a.x
106+
a.x; a.x; a.x; a.x; a.x
107+
a.x; a.x; a.x; a.x; a.x
108+
a.x; a.x; a.x; a.x; a.x
109+
110+
def read_boundmethod(trials=trials, a=A()):
111+
for t in trials:
112+
a.m; a.m; a.m; a.m; a.m
113+
a.m; a.m; a.m; a.m; a.m
114+
a.m; a.m; a.m; a.m; a.m
115+
a.m; a.m; a.m; a.m; a.m
116+
a.m; a.m; a.m; a.m; a.m
117+
118+
def write_local(trials=trials):
119+
v_local = 1
120+
for t in trials:
121+
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
122+
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
123+
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
124+
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
125+
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
126+
127+
def make_nonlocal_writer():
128+
v_nonlocal = 1
129+
def inner(trials=trials):
130+
nonlocal v_nonlocal
131+
for t in trials:
132+
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
133+
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
134+
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
135+
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
136+
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
137+
inner.__name__ = 'write_nonlocal'
138+
return inner
139+
140+
write_nonlocal = make_nonlocal_writer()
141+
142+
def write_global(trials=trials):
143+
global v_global
144+
for t in trials:
145+
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
146+
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
147+
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
148+
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
149+
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
150+
151+
def write_classvar(trials=trials, A=A):
152+
for t in trials:
153+
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
154+
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
155+
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
156+
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
157+
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
158+
159+
def write_instancevar(trials=trials, a=C(1)):
160+
for t in trials:
161+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
162+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
163+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
164+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
165+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
166+
167+
def write_instancevar_slots(trials=trials, a=B(1)):
168+
for t in trials:
169+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
170+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
171+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
172+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
173+
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
174+
175+
def read_list(trials=trials, a=[1]):
176+
for t in trials:
177+
a[0]; a[0]; a[0]; a[0]; a[0]
178+
a[0]; a[0]; a[0]; a[0]; a[0]
179+
a[0]; a[0]; a[0]; a[0]; a[0]
180+
a[0]; a[0]; a[0]; a[0]; a[0]
181+
a[0]; a[0]; a[0]; a[0]; a[0]
182+
183+
def read_deque(trials=trials, a=deque([1])):
184+
for t in trials:
185+
a[0]; a[0]; a[0]; a[0]; a[0]
186+
a[0]; a[0]; a[0]; a[0]; a[0]
187+
a[0]; a[0]; a[0]; a[0]; a[0]
188+
a[0]; a[0]; a[0]; a[0]; a[0]
189+
a[0]; a[0]; a[0]; a[0]; a[0]
190+
191+
def read_dict(trials=trials, a={0: 1}):
192+
for t in trials:
193+
a[0]; a[0]; a[0]; a[0]; a[0]
194+
a[0]; a[0]; a[0]; a[0]; a[0]
195+
a[0]; a[0]; a[0]; a[0]; a[0]
196+
a[0]; a[0]; a[0]; a[0]; a[0]
197+
a[0]; a[0]; a[0]; a[0]; a[0]
198+
199+
def list_append_pop(trials=trials, a=[1]):
200+
ap, pop = a.append, a.pop
201+
for t in trials:
202+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
203+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
204+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
205+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
206+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
207+
208+
def deque_append_pop(trials=trials, a=deque([1])):
209+
ap, pop = a.append, a.pop
210+
for t in trials:
211+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
212+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
213+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
214+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
215+
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
216+
217+
def write_list(trials=trials, a=[1]):
218+
for t in trials:
219+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
220+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
221+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
222+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
223+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
224+
225+
def write_deque(trials=trials, a=deque([1])):
226+
for t in trials:
227+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
228+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
229+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
230+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
231+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
232+
233+
def write_dict(trials=trials, a={0: 1}):
234+
for t in trials:
235+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
236+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
237+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
238+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
239+
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
240+
241+
def loop_overhead(trials=trials):
242+
for t in trials:
243+
pass
244+
245+
246+
if __name__=='__main__':
247+
248+
from timeit import Timer
249+
250+
for f in [
251+
'Variable and attribute read access:',
252+
read_local, read_nonlocal, read_global, read_builtin,
253+
read_classvar_from_class, read_classvar_from_instance,
254+
read_instancevar, read_instancevar_slots,
255+
read_namedtuple, read_boundmethod,
256+
'\nVariable and attribute write access:',
257+
write_local, write_nonlocal, write_global,
258+
write_classvar, write_instancevar, write_instancevar_slots,
259+
'\nData structure read access:',
260+
read_list, read_deque, read_dict,
261+
'\nData structure write access:',
262+
write_list, write_deque, write_dict,
263+
'\nStack (or queue) operations:',
264+
list_append_pop, deque_append_pop,
265+
'\nTiming loop overhead:',
266+
loop_overhead]:
267+
if isinstance(f, str):
268+
print(f)
269+
continue
270+
timing = min(Timer(f).repeat(7, 1000))
271+
timing *= 1000000 / (len(trials) * steps_per_trial)
272+
print(u'{:6.1f} \N{greek small letter mu}s\t{}'.format(timing, f.__name__))

0 commit comments

Comments
 (0)