Issue #25462: The hash of the key now is calculated only once in most

operations in C implementation of OrderedDict.
This commit is contained in:
Serhiy Storchaka 2015-11-13 15:18:26 +02:00
commit 1010921c71
2 changed files with 87 additions and 36 deletions

View File

@ -10,6 +10,9 @@ Release date: XXXX-XX-XX
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #25462: The hash of the key now is calculated only once in most
operations in C implementation of OrderedDict.
- Issue #22995: Default implementation of __reduce__ and __reduce_ex__ now - Issue #22995: Default implementation of __reduce__ and __reduce_ex__ now
rejects builtin types with not defined __new__. rejects builtin types with not defined __new__.

View File

@ -88,15 +88,16 @@ For adding nodes:
* _odict_add_head(od, node) * _odict_add_head(od, node)
* _odict_add_tail(od, node) * _odict_add_tail(od, node)
* _odict_add_new_node(od, key) * _odict_add_new_node(od, key, hash)
For removing nodes: For removing nodes:
* _odict_clear_node(od, node) * _odict_clear_node(od, node, key, hash)
* _odict_clear_nodes(od, clear_each) * _odict_clear_nodes(od, clear_each)
Others: Others:
* _odict_find_node_hash(od, key, hash)
* _odict_find_node(od, key) * _odict_find_node(od, key)
* _odict_keys_equal(od1, od2) * _odict_keys_equal(od1, od2)
@ -532,7 +533,7 @@ _odict_free_fast_nodes(PyODictObject *od) {
/* Return the index into the hash table, regardless of a valid node. */ /* Return the index into the hash table, regardless of a valid node. */
static Py_ssize_t static Py_ssize_t
_odict_get_index_hash(PyODictObject *od, PyObject *key, Py_hash_t hash) _odict_get_index_raw(PyODictObject *od, PyObject *key, Py_hash_t hash)
{ {
PyObject **value_addr = NULL; PyObject **value_addr = NULL;
PyDictKeyEntry *ep; PyDictKeyEntry *ep;
@ -563,7 +564,7 @@ _odict_resize(PyODictObject *od) {
/* Copy the current nodes into the table. */ /* Copy the current nodes into the table. */
_odict_FOREACH(od, node) { _odict_FOREACH(od, node) {
i = _odict_get_index_hash(od, _odictnode_KEY(node), i = _odict_get_index_raw(od, _odictnode_KEY(node),
_odictnode_HASH(node)); _odictnode_HASH(node));
if (i < 0) { if (i < 0) {
PyMem_FREE(fast_nodes); PyMem_FREE(fast_nodes);
@ -582,15 +583,11 @@ _odict_resize(PyODictObject *od) {
/* Return the index into the hash table, regardless of a valid node. */ /* Return the index into the hash table, regardless of a valid node. */
static Py_ssize_t static Py_ssize_t
_odict_get_index(PyODictObject *od, PyObject *key) _odict_get_index(PyODictObject *od, PyObject *key, Py_hash_t hash)
{ {
Py_hash_t hash;
PyDictKeysObject *keys; PyDictKeysObject *keys;
assert(key != NULL); assert(key != NULL);
hash = PyObject_Hash(key);
if (hash == -1)
return -1;
keys = ((PyDictObject *)od)->ma_keys; keys = ((PyDictObject *)od)->ma_keys;
/* Ensure od_fast_nodes and dk_entries are in sync. */ /* Ensure od_fast_nodes and dk_entries are in sync. */
@ -601,18 +598,35 @@ _odict_get_index(PyODictObject *od, PyObject *key)
return -1; return -1;
} }
return _odict_get_index_hash(od, key, hash); return _odict_get_index_raw(od, key, hash);
} }
/* Returns NULL if there was some error or the key was not found. */ /* Returns NULL if there was some error or the key was not found. */
static _ODictNode * static _ODictNode *
_odict_find_node(PyODictObject *od, PyObject *key) _odict_find_node_hash(PyODictObject *od, PyObject *key, Py_hash_t hash)
{ {
Py_ssize_t index; Py_ssize_t index;
if (_odict_EMPTY(od)) if (_odict_EMPTY(od))
return NULL; return NULL;
index = _odict_get_index(od, key); index = _odict_get_index(od, key, hash);
if (index < 0)
return NULL;
return od->od_fast_nodes[index];
}
static _ODictNode *
_odict_find_node(PyODictObject *od, PyObject *key)
{
Py_ssize_t index;
Py_hash_t hash;
if (_odict_EMPTY(od))
return NULL;
hash = PyObject_Hash(key);
if (hash == -1)
return NULL;
index = _odict_get_index(od, key, hash);
if (index < 0) if (index < 0)
return NULL; return NULL;
return od->od_fast_nodes[index]; return od->od_fast_nodes[index];
@ -646,18 +660,13 @@ _odict_add_tail(PyODictObject *od, _ODictNode *node)
/* adds the node to the end of the list */ /* adds the node to the end of the list */
static int static int
_odict_add_new_node(PyODictObject *od, PyObject *key) _odict_add_new_node(PyODictObject *od, PyObject *key, Py_hash_t hash)
{ {
Py_hash_t hash;
Py_ssize_t i; Py_ssize_t i;
_ODictNode *node; _ODictNode *node;
hash = PyObject_Hash(key);
if (hash == -1)
return -1;
Py_INCREF(key); Py_INCREF(key);
i = _odict_get_index(od, key); i = _odict_get_index(od, key, hash);
if (i < 0) { if (i < 0) {
if (!PyErr_Occurred()) if (!PyErr_Occurred())
PyErr_SetObject(PyExc_KeyError, key); PyErr_SetObject(PyExc_KeyError, key);
@ -728,7 +737,8 @@ _odict_remove_node(PyODictObject *od, _ODictNode *node)
we modify od_fast_nodes. we modify od_fast_nodes.
*/ */
static int static int
_odict_clear_node(PyODictObject *od, _ODictNode *node, PyObject *key) _odict_clear_node(PyODictObject *od, _ODictNode *node, PyObject *key,
Py_hash_t hash)
{ {
Py_ssize_t i; Py_ssize_t i;
@ -738,7 +748,7 @@ _odict_clear_node(PyODictObject *od, _ODictNode *node, PyObject *key)
return 0; return 0;
} }
i = _odict_get_index(od, key); i = _odict_get_index(od, key, hash);
if (i < 0) if (i < 0)
return PyErr_Occurred() ? -1 : 0; return PyErr_Occurred() ? -1 : 0;
@ -1091,7 +1101,8 @@ odict_pop(PyObject *od, PyObject *args, PyObject *kwargs)
} }
static PyObject * static PyObject *
_odict_popkey(PyObject *od, PyObject *key, PyObject *failobj) _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj,
Py_hash_t hash)
{ {
_ODictNode *node; _ODictNode *node;
PyObject *value = NULL; PyObject *value = NULL;
@ -1099,13 +1110,13 @@ _odict_popkey(PyObject *od, PyObject *key, PyObject *failobj)
/* Pop the node first to avoid a possible dict resize (due to /* Pop the node first to avoid a possible dict resize (due to
eval loop reentrancy) and complications due to hash collision eval loop reentrancy) and complications due to hash collision
resolution. */ resolution. */
node = _odict_find_node((PyODictObject *)od, key); node = _odict_find_node_hash((PyODictObject *)od, key, hash);
if (node == NULL) { if (node == NULL) {
if (PyErr_Occurred()) if (PyErr_Occurred())
return NULL; return NULL;
} }
else { else {
int res = _odict_clear_node((PyODictObject *)od, node, key); int res = _odict_clear_node((PyODictObject *)od, node, key, hash);
if (res < 0) { if (res < 0) {
return NULL; return NULL;
} }
@ -1114,8 +1125,14 @@ _odict_popkey(PyObject *od, PyObject *key, PyObject *failobj)
/* Now delete the value from the dict. */ /* Now delete the value from the dict. */
if (PyODict_CheckExact(od)) { if (PyODict_CheckExact(od)) {
if (node != NULL) { if (node != NULL) {
/* We could do PyDict_GetItem() and PyDict_DelItem() directly... */ value = _PyDict_GetItem_KnownHash(od, key, hash); /* borrowed */
value = _PyDict_Pop((PyDictObject *)od, key, failobj); if (value != NULL) {
Py_INCREF(value);
if (_PyDict_DelItem_KnownHash(od, key, hash) < 0) {
Py_DECREF(value);
return NULL;
}
}
} }
} }
else { else {
@ -1146,6 +1163,16 @@ _odict_popkey(PyObject *od, PyObject *key, PyObject *failobj)
return value; return value;
} }
static PyObject *
_odict_popkey(PyObject *od, PyObject *key, PyObject *failobj)
{
Py_hash_t hash = PyObject_Hash(key);
if (hash == -1)
return NULL;
return _odict_popkey_hash(od, key, failobj, hash);
}
/* popitem() */ /* popitem() */
PyDoc_STRVAR(odict_popitem__doc__, PyDoc_STRVAR(odict_popitem__doc__,
@ -1178,7 +1205,7 @@ odict_popitem(PyObject *od, PyObject *args, PyObject *kwargs)
node = last ? _odict_LAST(od) : _odict_FIRST(od); node = last ? _odict_LAST(od) : _odict_FIRST(od);
key = _odictnode_KEY(node); key = _odictnode_KEY(node);
Py_INCREF(key); Py_INCREF(key);
value = _odict_popkey(od, key, NULL); value = _odict_popkey_hash(od, key, NULL, _odictnode_HASH(node));
if (value == NULL) if (value == NULL)
return NULL; return NULL;
item = PyTuple_Pack(2, key, value); item = PyTuple_Pack(2, key, value);
@ -1237,6 +1264,10 @@ odict_clear(register PyODictObject *od)
/* copy() */ /* copy() */
/* forward */
static int _PyODict_SetItem_KnownHash(PyObject *, PyObject *, PyObject *,
Py_hash_t);
PyDoc_STRVAR(odict_copy__doc__, "od.copy() -> a shallow copy of od"); PyDoc_STRVAR(odict_copy__doc__, "od.copy() -> a shallow copy of od");
static PyObject * static PyObject *
@ -1261,7 +1292,8 @@ odict_copy(register PyODictObject *od)
PyErr_SetObject(PyExc_KeyError, key); PyErr_SetObject(PyExc_KeyError, key);
goto fail; goto fail;
} }
if (PyODict_SetItem((PyObject *)od_copy, key, value) != 0) if (_PyODict_SetItem_KnownHash((PyObject *)od_copy, key, value,
_odictnode_HASH(node)) != 0)
goto fail; goto fail;
} }
} }
@ -1720,16 +1752,18 @@ PyODict_New(void) {
return odict_new(&PyODict_Type, NULL, NULL); return odict_new(&PyODict_Type, NULL, NULL);
}; };
int static int
PyODict_SetItem(PyObject *od, PyObject *key, PyObject *value) { _PyODict_SetItem_KnownHash(PyObject *od, PyObject *key, PyObject *value,
int res = PyDict_SetItem(od, key, value); Py_hash_t hash)
{
int res = _PyDict_SetItem_KnownHash(od, key, value, hash);
if (res == 0) { if (res == 0) {
res = _odict_add_new_node((PyODictObject *)od, key); res = _odict_add_new_node((PyODictObject *)od, key, hash);
if (res < 0) { if (res < 0) {
/* Revert setting the value on the dict */ /* Revert setting the value on the dict */
PyObject *exc, *val, *tb; PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb); PyErr_Fetch(&exc, &val, &tb);
(void) PyDict_DelItem(od, key); (void) _PyDict_DelItem_KnownHash(od, key, hash);
_PyErr_ChainExceptions(exc, val, tb); _PyErr_ChainExceptions(exc, val, tb);
} }
} }
@ -1737,11 +1771,25 @@ PyODict_SetItem(PyObject *od, PyObject *key, PyObject *value) {
}; };
int int
PyODict_DelItem(PyObject *od, PyObject *key) { PyODict_SetItem(PyObject *od, PyObject *key, PyObject *value)
int res = _odict_clear_node((PyODictObject *)od, NULL, key); {
Py_hash_t hash = PyObject_Hash(key);
if (hash == -1)
return -1;
return _PyODict_SetItem_KnownHash(od, key, value, hash);
};
int
PyODict_DelItem(PyObject *od, PyObject *key)
{
int res;
Py_hash_t hash = PyObject_Hash(key);
if (hash == -1)
return -1;
res = _odict_clear_node((PyODictObject *)od, NULL, key, hash);
if (res < 0) if (res < 0)
return -1; return -1;
return PyDict_DelItem(od, key); return _PyDict_DelItem_KnownHash(od, key, hash);
}; };