Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,11 @@ CPython bytecode changes
* Added new opcode :opcode:`END_ASYNC_FOR` for handling exceptions raised
when awaiting a next item in an :keyword:`async for` loop.
(Contributed by Serhiy Storchaka in :issue:`33041`.)


Demos and Tools
---------------

* Added a benchmark script for timing various ways to access variables:
``Tools/scripts/var_access_benchmark.py``.
(Contributed by Raymond Hettinger in :issue:`35884`.)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a benchmark script for timing various ways to access variables:
``Tools/scripts/var_access_benchmark.py``.
272 changes: 272 additions & 0 deletions Tools/scripts/var_access_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
'Show relative speeds of local, nonlocal, global, and built-in access.'

# Please leave this code so that it runs under older versions of
# Python 3 (no f-strings). That will allow benchmarking for
# cross-version comparisons. To run the benchmark on Python 2,
# comment-out the nonlocal reads and writes.

from collections import deque, namedtuple

trials = [None] * 500
steps_per_trial = 25

class A(object):
def m(self):
pass

class B(object):
__slots__ = 'x'
def __init__(self, x):
self.x = x

class C(object):
def __init__(self, x):
self.x = x

def read_local(trials=trials):
v_local = 1
for t in trials:
v_local; v_local; v_local; v_local; v_local
v_local; v_local; v_local; v_local; v_local
v_local; v_local; v_local; v_local; v_local
v_local; v_local; v_local; v_local; v_local
v_local; v_local; v_local; v_local; v_local

def make_nonlocal_reader():
v_nonlocal = 1
def inner(trials=trials):
for t in trials:
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal; v_nonlocal
inner.__name__ = 'read_nonlocal'
return inner

read_nonlocal = make_nonlocal_reader()

v_global = 1
def read_global(trials=trials):
for t in trials:
v_global; v_global; v_global; v_global; v_global
v_global; v_global; v_global; v_global; v_global
v_global; v_global; v_global; v_global; v_global
v_global; v_global; v_global; v_global; v_global
v_global; v_global; v_global; v_global; v_global

def read_builtin(trials=trials):
for t in trials:
oct; oct; oct; oct; oct
oct; oct; oct; oct; oct
oct; oct; oct; oct; oct
oct; oct; oct; oct; oct
oct; oct; oct; oct; oct

def read_classvar_from_class(trials=trials, A=A):
A.x = 1
for t in trials:
A.x; A.x; A.x; A.x; A.x
A.x; A.x; A.x; A.x; A.x
A.x; A.x; A.x; A.x; A.x
A.x; A.x; A.x; A.x; A.x
A.x; A.x; A.x; A.x; A.x

def read_classvar_from_instance(trials=trials, A=A):
A.x = 1
a = A()
for t in trials:
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x

def read_instancevar(trials=trials, a=C(1)):
for t in trials:
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x

def read_instancevar_slots(trials=trials, a=B(1)):
for t in trials:
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x

def read_namedtuple(trials=trials, D=namedtuple('D', ['x'])):
a = D(1)
for t in trials:
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x
a.x; a.x; a.x; a.x; a.x

def read_boundmethod(trials=trials, a=A()):
for t in trials:
a.m; a.m; a.m; a.m; a.m
a.m; a.m; a.m; a.m; a.m
a.m; a.m; a.m; a.m; a.m
a.m; a.m; a.m; a.m; a.m
a.m; a.m; a.m; a.m; a.m

def write_local(trials=trials):
v_local = 1
for t in trials:
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1
v_local = 1; v_local = 1; v_local = 1; v_local = 1; v_local = 1

def make_nonlocal_writer():
v_nonlocal = 1
def inner(trials=trials):
nonlocal v_nonlocal
for t in trials:
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1; v_nonlocal = 1
inner.__name__ = 'write_nonlocal'
return inner

write_nonlocal = make_nonlocal_writer()

def write_global(trials=trials):
global v_global
for t in trials:
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1
v_global = 1; v_global = 1; v_global = 1; v_global = 1; v_global = 1

def write_classvar(trials=trials, A=A):
for t in trials:
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1
A.x = 1; A.x = 1; A.x = 1; A.x = 1; A.x = 1

def write_instancevar(trials=trials, a=C(1)):
for t in trials:
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1

def write_instancevar_slots(trials=trials, a=B(1)):
for t in trials:
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1
a.x = 1; a.x = 1; a.x = 1; a.x = 1; a.x = 1

def read_list(trials=trials, a=[1]):
for t in trials:
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]

def read_deque(trials=trials, a=deque([1])):
for t in trials:
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]

def read_dict(trials=trials, a={0: 1}):
for t in trials:
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]
a[0]; a[0]; a[0]; a[0]; a[0]

def list_append_pop(trials=trials, a=[1]):
ap, pop = a.append, a.pop
for t in trials:
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();

def deque_append_pop(trials=trials, a=deque([1])):
ap, pop = a.append, a.pop
for t in trials:
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();
ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop(); ap(1); pop();

def write_list(trials=trials, a=[1]):
for t in trials:
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1

def write_deque(trials=trials, a=deque([1])):
for t in trials:
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1

def write_dict(trials=trials, a={0: 1}):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a dict read/write benchmark with short str keys would probably also be interesting in comparison.

for t in trials:
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1
a[0]=1; a[0]=1; a[0]=1; a[0]=1; a[0]=1

def loop_overhead(trials=trials):
for t in trials:
pass


if __name__=='__main__':

from timeit import Timer

for f in [
'Variable and attribute read access:',
read_local, read_nonlocal, read_global, read_builtin,
read_classvar_from_class, read_classvar_from_instance,
read_instancevar, read_instancevar_slots,
read_namedtuple, read_boundmethod,
'\nVariable and attribute write access:',
write_local, write_nonlocal, write_global,
write_classvar, write_instancevar, write_instancevar_slots,
'\nData structure read access:',
read_list, read_deque, read_dict,
'\nData structure write access:',
write_list, write_deque, write_dict,
'\nStack (or queue) operations:',
list_append_pop, deque_append_pop,
'\nTiming loop overhead:',
loop_overhead]:
if isinstance(f, str):
print(f)
continue
timing = min(Timer(f).repeat(7, 1000))
timing *= 1000000 / (len(trials) * steps_per_trial)
print(u'{:6.1f} \N{greek small letter mu}s\t{}'.format(timing, f.__name__))