diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 09c43b1f30a..a3982b0dfe0 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -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`.) diff --git a/Misc/NEWS.d/next/Tools-Demos/2019-02-01-12-22-37.bpo-35884.hJkMRD.rst b/Misc/NEWS.d/next/Tools-Demos/2019-02-01-12-22-37.bpo-35884.hJkMRD.rst new file mode 100644 index 00000000000..416eeac1d93 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2019-02-01-12-22-37.bpo-35884.hJkMRD.rst @@ -0,0 +1,2 @@ +Add a benchmark script for timing various ways to access variables: +``Tools/scripts/var_access_benchmark.py``. diff --git a/Tools/scripts/var_access_benchmark.py b/Tools/scripts/var_access_benchmark.py new file mode 100644 index 00000000000..b4f3b972705 --- /dev/null +++ b/Tools/scripts/var_access_benchmark.py @@ -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}): + 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__))