Skip to content

Commit b9d98d5

Browse files
Issue python#24483: C implementation of functools.lru_cache() now calculates key's
hash only once.
1 parent c9fda9b commit b9d98d5

4 files changed

Lines changed: 64 additions & 6 deletions

File tree

Include/dictobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ PyAPI_FUNC(int) _PyDict_SetItem_KnownHash(PyObject *mp, PyObject *key,
7272
PyObject *item, Py_hash_t hash);
7373
#endif
7474
PyAPI_FUNC(int) PyDict_DelItem(PyObject *mp, PyObject *key);
75+
#ifndef Py_LIMITED_API
76+
PyAPI_FUNC(int) _PyDict_DelItem_KnownHash(PyObject *mp, PyObject *key,
77+
Py_hash_t hash);
78+
#endif
7579
PyAPI_FUNC(void) PyDict_Clear(PyObject *mp);
7680
PyAPI_FUNC(int) PyDict_Next(
7781
PyObject *mp, Py_ssize_t *pos, PyObject **key, PyObject **value);

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ Core and Builtins
3232
Library
3333
-------
3434

35+
- Issue #24483: C implementation of functools.lru_cache() now calculates key's
36+
hash only once.
37+
3538
- Issue #22958: Constructor and update method of weakref.WeakValueDictionary
3639
now accept the self and the dict keyword arguments.
3740

Modules/_functoolsmodule.c

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ struct lru_cache_object;
601601
typedef struct lru_list_elem {
602602
PyObject_HEAD
603603
struct lru_list_elem *prev, *next; /* borrowed links */
604+
Py_hash_t hash;
604605
PyObject *key, *result;
605606
} lru_list_elem;
606607

@@ -762,10 +763,14 @@ static PyObject *
762763
infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds)
763764
{
764765
PyObject *result;
766+
Py_hash_t hash;
765767
PyObject *key = lru_cache_make_key(args, kwds, self->typed);
766768
if (!key)
767769
return NULL;
768-
result = PyDict_GetItemWithError(self->cache, key);
770+
hash = PyObject_Hash(key);
771+
if (hash == -1)
772+
return NULL;
773+
result = _PyDict_GetItem_KnownHash(self->cache, key, hash);
769774
if (result) {
770775
Py_INCREF(result);
771776
self->hits++;
@@ -781,7 +786,7 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd
781786
Py_DECREF(key);
782787
return NULL;
783788
}
784-
if (PyDict_SetItem(self->cache, key, result) < 0) {
789+
if (_PyDict_SetItem_KnownHash(self->cache, key, result, hash) < 0) {
785790
Py_DECREF(result);
786791
Py_DECREF(key);
787792
return NULL;
@@ -813,11 +818,15 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
813818
{
814819
lru_list_elem *link;
815820
PyObject *key, *result;
821+
Py_hash_t hash;
816822

817823
key = lru_cache_make_key(args, kwds, self->typed);
818824
if (!key)
819825
return NULL;
820-
link = (lru_list_elem *)PyDict_GetItemWithError(self->cache, key);
826+
hash = PyObject_Hash(key);
827+
if (hash == -1)
828+
return NULL;
829+
link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, key, hash);
821830
if (link) {
822831
lru_cache_extricate_link(link);
823832
lru_cache_append_link(self, link);
@@ -845,7 +854,8 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
845854
/* Remove it from the cache.
846855
The cache dict holds one reference to the link,
847856
and the linked list holds yet one reference to it. */
848-
if (PyDict_DelItem(self->cache, link->key) < 0) {
857+
if (_PyDict_DelItem_KnownHash(self->cache, link->key,
858+
link->hash) < 0) {
849859
lru_cache_append_link(self, link);
850860
Py_DECREF(key);
851861
Py_DECREF(result);
@@ -859,9 +869,11 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
859869
oldkey = link->key;
860870
oldresult = link->result;
861871

872+
link->hash = hash;
862873
link->key = key;
863874
link->result = result;
864-
if (PyDict_SetItem(self->cache, key, (PyObject *)link) < 0) {
875+
if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link,
876+
hash) < 0) {
865877
Py_DECREF(link);
866878
Py_DECREF(oldkey);
867879
Py_DECREF(oldresult);
@@ -881,10 +893,12 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
881893
return NULL;
882894
}
883895

896+
link->hash = hash;
884897
link->key = key;
885898
link->result = result;
886899
_PyObject_GC_TRACK(link);
887-
if (PyDict_SetItem(self->cache, key, (PyObject *)link) < 0) {
900+
if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link,
901+
hash) < 0) {
888902
Py_DECREF(link);
889903
return NULL;
890904
}

Objects/dictobject.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
12421242
}
12431243
assert(key);
12441244
assert(value);
1245+
assert(hash != -1);
12451246
mp = (PyDictObject *)op;
12461247

12471248
/* insertdict() handles any resizing that might be necessary */
@@ -1290,6 +1291,42 @@ PyDict_DelItem(PyObject *op, PyObject *key)
12901291
return 0;
12911292
}
12921293

1294+
int
1295+
_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
1296+
{
1297+
PyDictObject *mp;
1298+
PyDictKeyEntry *ep;
1299+
PyObject *old_key, *old_value;
1300+
PyObject **value_addr;
1301+
1302+
if (!PyDict_Check(op)) {
1303+
PyErr_BadInternalCall();
1304+
return -1;
1305+
}
1306+
assert(key);
1307+
assert(hash != -1);
1308+
mp = (PyDictObject *)op;
1309+
ep = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr);
1310+
if (ep == NULL)
1311+
return -1;
1312+
if (*value_addr == NULL) {
1313+
_PyErr_SetKeyError(key);
1314+
return -1;
1315+
}
1316+
old_value = *value_addr;
1317+
*value_addr = NULL;
1318+
mp->ma_used--;
1319+
if (!_PyDict_HasSplitTable(mp)) {
1320+
ENSURE_ALLOWS_DELETIONS(mp);
1321+
old_key = ep->me_key;
1322+
Py_INCREF(dummy);
1323+
ep->me_key = dummy;
1324+
Py_DECREF(old_key);
1325+
}
1326+
Py_DECREF(old_value);
1327+
return 0;
1328+
}
1329+
12931330
void
12941331
PyDict_Clear(PyObject *op)
12951332
{

0 commit comments

Comments
 (0)