Why is dict definition faster in Python 2.7 than in Python 3.x? -
i have encountered (not unusual) situation in had either use map()
or list comprehension expression. , wondered 1 faster.
this stackoverflow answer provided me solution, started test myself. results same, found unexpected behavior when switching python 3 got curious about, , namely:
λ iulian-pc ~ → python --version python 2.7.6 λ iulian-pc ~ → python3 --version python 3.4.3 λ iulian-pc ~ → python -mtimeit '{}' 10000000 loops, best of 3: 0.0306 usec per loop λ iulian-pc ~ → python3 -mtimeit '{}' 10000000 loops, best of 3: 0.105 usec per loop λ iulian-pc ~ → python -mtimeit 'dict()' 10000000 loops, best of 3: 0.103 usec per loop λ iulian-pc ~ → python3 -mtimeit 'dict()' 10000000 loops, best of 3: 0.165 usec per loop
i had assumption python 3 faster python 2, turned out in several posts (1, 2) it's not case. thought maybe python 3.5 perform better @ such simple task, state in readme
:
the language same, many details, how built-in objects dictionaries , strings work, have changed considerably, , lot of deprecated features have been removed.
but nope, performed worse:
λ iulian-pc ~ → python3 --version python 3.5.0 λ iulian-pc ~ → python3 -mtimeit '{}' 10000000 loops, best of 3: 0.144 usec per loop λ iulian-pc ~ → python3 -mtimeit 'dict()' 1000000 loops, best of 3: 0.217 usec per loop
i've tried dive python 3.5 source code dict
, knowledge of c language not sufficient find answer myself (or, maybe don't search in right place).
so, question is:
what makes newer version of python slower comparing older version of python on relatively simple task such dict
definition, common sense should vice-versa? i'm aware of fact these differences small in cases can neglected. observation made me curious why time increased , not remained same @ least?
let's disassemble {}
:
>>> dis import dis >>> dis(lambda: {}) 1 0 build_map 0 3 return_value
python 2.7 implementation of build_map
target(build_map) { x = _pydict_newpresized((py_ssize_t)oparg); push(x); if (x != null) dispatch(); break; }
python 3.5 implementation of build_map
target(build_map) { int i; pyobject *map = _pydict_newpresized((py_ssize_t)oparg); if (map == null) goto error; (i = oparg; > 0; i--) { int err; pyobject *key = peek(2*i); pyobject *value = peek(2*i - 1); err = pydict_setitem(map, key, value); if (err != 0) { py_decref(map); goto error; } } while (oparg--) { py_decref(pop()); py_decref(pop()); } push(map); dispatch(); }
it's little bit more code.
edit:
python 3.4 implementation of build_map id same 2.7 (thanks @user2357112). dig deeper , it's looks python 3 min size of dict 8 pydict_minsize_combined const
pydict_minsize_combined starting size new, non-split dict. 8 allows dicts no more 5 active entries; experiments suggested suffices majority of dicts (consisting of usually-small dicts created pass keyword arguments). making 8, rather 4 reduces number of resizes dictionaries, without significant memory use.
look @ _pydict_newpresized in python 3.4
pyobject * _pydict_newpresized(py_ssize_t minused) { py_ssize_t newsize; pydictkeysobject *new_keys; (newsize = pydict_minsize_combined; newsize <= minused && newsize > 0; newsize <<= 1) ; new_keys = new_keys_object(newsize); if (new_keys == null) return null; return new_dict(new_keys, null); }
and in 2.7
pyobject * _pydict_newpresized(py_ssize_t minused) { pyobject *op = pydict_new(); if (minused>5 && op != null && dictresize((pydictobject *)op, minused) == -1) { py_decref(op); return null; } return op; }
in both cases minused
has value 1.
python 2.7 create empty dict , python 3.4 create 7-element dict.
Comments
Post a Comment