diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index a571045bcc..a703534c71 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -664,73 +664,12 @@ sequence is empty."); /* this object is used delimit args and keywords in the cache keys */ static PyObject *kwd_mark = NULL; -struct lru_list_elem; struct lru_cache_object; -typedef struct lru_list_elem { - PyObject_HEAD - struct lru_list_elem *prev, *next; /* borrowed links */ - Py_hash_t hash; - PyObject *key, *result; -} lru_list_elem; - -static void -lru_list_elem_dealloc(lru_list_elem *link) -{ - _PyObject_GC_UNTRACK(link); - Py_XDECREF(link->key); - Py_XDECREF(link->result); - PyObject_GC_Del(link); -} - -static int -lru_list_elem_traverse(lru_list_elem *link, visitproc visit, void *arg) -{ - Py_VISIT(link->key); - Py_VISIT(link->result); - return 0; -} - -static int -lru_list_elem_clear(lru_list_elem *link) -{ - Py_CLEAR(link->key); - Py_CLEAR(link->result); - return 0; -} - -static PyTypeObject lru_list_elem_type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "functools._lru_list_elem", /* tp_name */ - sizeof(lru_list_elem), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)lru_list_elem_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ - 0, /* tp_doc */ - (traverseproc)lru_list_elem_traverse, /* tp_traverse */ - (inquiry)lru_list_elem_clear, /* tp_clear */ -}; - - typedef PyObject *(*lru_cache_ternaryfunc)(struct lru_cache_object *, PyObject *, PyObject *); typedef struct lru_cache_object { - lru_list_elem root; /* includes PyObject_HEAD */ + PyObject_HEAD Py_ssize_t maxsize; PyObject *maxsize_O; PyObject *func; @@ -854,31 +793,15 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd return result; } -static void -lru_cache_extricate_link(lru_list_elem *link) -{ - link->prev->next = link->next; - link->next->prev = link->prev; -} - -static void -lru_cache_append_link(lru_cache_object *self, lru_list_elem *link) -{ - lru_list_elem *root = &self->root; - lru_list_elem *last = root->prev; - last->next = root->prev = link; - link->prev = last; - link->next = root; -} +PyObject * +_PyDict_MoveToEnd_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash); static PyObject * bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds) { - lru_list_elem *link; - PyObject *key, *result; + PyObject *result; Py_hash_t hash; - - key = lru_cache_make_key(args, kwds, self->typed); + PyObject *key = lru_cache_make_key(args, kwds, self->typed); if (!key) return NULL; hash = PyObject_Hash(key); @@ -886,13 +809,10 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds Py_DECREF(key); return NULL; } - link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, key, hash); - if (link) { - lru_cache_extricate_link(link); - lru_cache_append_link(self, link); - self->hits++; - result = link->result; + result = _PyDict_MoveToEnd_KnownHash((PyDictObject *)self->cache, key, hash); + if (result) { Py_INCREF(result); + self->hits++; Py_DECREF(key); return result; } @@ -905,81 +825,33 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds Py_DECREF(key); return NULL; } - if (self->full && self->root.next != &self->root) { - /* Use the oldest item to store the new key and result. */ - PyObject *oldkey, *oldresult, *popresult; - /* Extricate the oldest item. */ - link = self->root.next; - lru_cache_extricate_link(link); - /* Remove it from the cache. - The cache dict holds one reference to the link, - and the linked list holds yet one reference to it. */ - popresult = _PyDict_Pop_KnownHash(self->cache, - link->key, link->hash, - Py_None); - if (popresult == Py_None) { - /* Getting here means that this same key was added to the - cache while the lock was released. Since the link - update is already done, we need only return the - computed result and update the count of misses. */ - Py_DECREF(popresult); - Py_DECREF(link); - Py_DECREF(key); - } - else if (popresult == NULL) { - lru_cache_append_link(self, link); - Py_DECREF(key); - Py_DECREF(result); - return NULL; - } - else { - Py_DECREF(popresult); - /* Keep a reference to the old key and old result to - prevent their ref counts from going to zero during the - update. That will prevent potentially arbitrary object - clean-up code (i.e. __del__) from running while we're - still adjusting the links. */ - oldkey = link->key; - oldresult = link->result; - - link->hash = hash; - link->key = key; - link->result = result; - if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link, - hash) < 0) { - Py_DECREF(link); - Py_DECREF(oldkey); - Py_DECREF(oldresult); + if (self->full) { + /* Remove the first item from the cache. */ + Py_ssize_t pos = 0; + PyObject *firstkey; + Py_hash_t firsthash; + if (_PyDict_Next(self->cache, &pos, &firstkey, NULL, &firsthash)) { + PyObject *popresult; + Py_INCREF(firstkey); + popresult = _PyDict_Pop_KnownHash(self->cache, + firstkey, firsthash, + Py_None); + Py_DECREF(firstkey); + if (popresult == NULL) { + Py_DECREF(result); + Py_DECREF(key); return NULL; } - lru_cache_append_link(self, link); - Py_INCREF(result); /* for return */ - Py_DECREF(oldkey); - Py_DECREF(oldresult); - } - } else { - /* Put result in a new link at the front of the queue. */ - link = (lru_list_elem *)PyObject_GC_New(lru_list_elem, - &lru_list_elem_type); - if (link == NULL) { - Py_DECREF(key); - Py_DECREF(result); - return NULL; - } - - link->hash = hash; - link->key = key; - link->result = result; - _PyObject_GC_TRACK(link); - if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link, - hash) < 0) { - Py_DECREF(link); - return NULL; + Py_DECREF(popresult); } - lru_cache_append_link(self, link); - Py_INCREF(result); /* for return */ - self->full = (PyDict_GET_SIZE(self->cache) >= self->maxsize); } + if (_PyDict_SetItem_KnownHash(self->cache, key, result, hash) < 0) { + Py_DECREF(result); + Py_DECREF(key); + return NULL; + } + self->full = (PyDict_GET_SIZE(self->cache) >= self->maxsize); + Py_DECREF(key); self->misses++; return result; } @@ -1035,8 +907,6 @@ lru_cache_new(PyTypeObject *type, PyObject *args, PyObject *kw) } obj->cache = cachedict; - obj->root.prev = &obj->root; - obj->root.next = &obj->root; obj->maxsize = maxsize; Py_INCREF(maxsize_O); obj->maxsize_O = maxsize_O; @@ -1051,42 +921,16 @@ lru_cache_new(PyTypeObject *type, PyObject *args, PyObject *kw) return (PyObject *)obj; } -static lru_list_elem * -lru_cache_unlink_list(lru_cache_object *self) -{ - lru_list_elem *root = &self->root; - lru_list_elem *link = root->next; - if (link == root) - return NULL; - root->prev->next = NULL; - root->next = root->prev = root; - return link; -} - -static void -lru_cache_clear_list(lru_list_elem *link) -{ - while (link != NULL) { - lru_list_elem *next = link->next; - Py_DECREF(link); - link = next; - } -} - static void lru_cache_dealloc(lru_cache_object *obj) { - lru_list_elem *list; /* bpo-31095: UnTrack is needed before calling any callbacks */ PyObject_GC_UnTrack(obj); - - list = lru_cache_unlink_list(obj); Py_XDECREF(obj->maxsize_O); Py_XDECREF(obj->func); Py_XDECREF(obj->cache); Py_XDECREF(obj->dict); Py_XDECREF(obj->cache_info_type); - lru_cache_clear_list(list); Py_TYPE(obj)->tp_free(obj); } @@ -1117,11 +961,9 @@ lru_cache_cache_info(lru_cache_object *self, PyObject *unused) static PyObject * lru_cache_cache_clear(lru_cache_object *self, PyObject *unused) { - lru_list_elem *list = lru_cache_unlink_list(self); self->hits = self->misses = 0; self->full = 0; PyDict_Clear(self->cache); - lru_cache_clear_list(list); Py_RETURN_NONE; } @@ -1148,12 +990,6 @@ lru_cache_deepcopy(PyObject *self, PyObject *unused) static int lru_cache_tp_traverse(lru_cache_object *self, visitproc visit, void *arg) { - lru_list_elem *link = self->root.next; - while (link != &self->root) { - lru_list_elem *next = link->next; - Py_VISIT(link); - link = next; - } Py_VISIT(self->maxsize_O); Py_VISIT(self->func); Py_VISIT(self->cache); @@ -1165,13 +1001,11 @@ lru_cache_tp_traverse(lru_cache_object *self, visitproc visit, void *arg) static int lru_cache_tp_clear(lru_cache_object *self) { - lru_list_elem *list = lru_cache_unlink_list(self); Py_CLEAR(self->maxsize_O); Py_CLEAR(self->func); Py_CLEAR(self->cache); Py_CLEAR(self->cache_info_type); Py_CLEAR(self->dict); - lru_cache_clear_list(list); return 0; } diff --git a/Objects/dictobject.c b/Objects/dictobject.c index b20b85c909..f40224811e 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1746,6 +1746,54 @@ _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *deflt) return _PyDict_Pop_KnownHash(dict, key, hash, deflt); } +PyObject * +_PyDict_MoveToEnd_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash) +{ + Py_ssize_t ix, hashpos; + PyObject *value; + PyDictKeyEntry *ep, *ep0; + + if (mp->ma_used == 0) { + return NULL; + } + + // Split table doesn't allow deletion. Combine it. + if (_PyDict_HasSplitTable(mp) || mp->ma_keys->dk_usable <= 0) { + if (insertion_resize(mp) < 0) { + return NULL; + } + } + assert(!_PyDict_HasSplitTable(mp)); + + ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value); + if (ix == DKIX_ERROR) + return NULL; + if (ix == DKIX_EMPTY || value == NULL) { + return NULL; + } + + hashpos = lookdict_index(mp->ma_keys, hash, ix); + assert(hashpos >= 0); + assert(value != NULL); + ep0 = DK_ENTRIES(mp->ma_keys); + ep = &ep0[ix]; + key = ep->me_key; + ep->me_key = NULL; + ep->me_value = NULL; + + ep = &ep0[mp->ma_keys->dk_nentries]; + dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); + ep->me_key = key; + ep->me_hash = hash; + ep->me_value = value; + mp->ma_version_tag = DICT_NEXT_VERSION(); + mp->ma_keys->dk_usable--; + mp->ma_keys->dk_nentries++; + assert(mp->ma_keys->dk_usable >= 0); + assert(_PyDict_CheckConsistency(mp)); + return value; +} + /* Internal version of dict.from_keys(). It is subclass-friendly. */ PyObject * _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)