Skip to content
Prev Previous commit
Next Next commit
Revert LRU cache element optimisation
This reverts commit 6d7cdaf.
  • Loading branch information
Erlend E. Aasland committed May 27, 2021
commit fc39917b8808c70553ac4606154dfb628b559134
32 changes: 26 additions & 6 deletions Modules/_functoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -762,10 +762,28 @@ typedef struct lru_list_elem {
PyObject *key, *result;
} lru_list_elem;

static int
lru_list_elem_clear(lru_list_elem *link)
{
Py_CLEAR(link->key);
Py_CLEAR(link->result);
return 0;
}

static int
lru_list_elem_traverse(lru_list_elem *link, visitproc visit, void *arg)
{
Py_VISIT(link->key);
Py_VISIT(link->result);
Py_VISIT(Py_TYPE(link));
return 0;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lru_list_elem_clear() function would also be needed to implement the GC protocol, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lru_list_elem_clear() function would also be needed to implement the GC protocol, no?

I was under the impression traverse was enough. I may be wrong.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without clear, the ref count cannot each zero, and the GC cannot break cycles: https://devguide.python.org/garbage_collector/#destroying-unreachable-objects

cc @pablogsal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_lru_list_elem is not a standalone type. It is an internal of _lru_cache_wrapper.

  • lru_cache_tp_traverse() visits lru_list_elem.
  • lru_cache_tp_clear() clears lru_list_elem.

That's why we can keep _lru_list_elem non-GC type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, Inada's #26416 is acceptable.


static void
lru_list_elem_dealloc(lru_list_elem *link)
{
PyTypeObject *tp = Py_TYPE(link);
PyObject_GC_UnTrack(link);
Py_XDECREF(link->key);
Py_XDECREF(link->result);
tp->tp_free(link);
Expand All @@ -774,13 +792,16 @@ lru_list_elem_dealloc(lru_list_elem *link)

static PyType_Slot lru_list_elem_type_slots[] = {
{Py_tp_dealloc, lru_list_elem_dealloc},
{Py_tp_traverse, lru_list_elem_traverse},
{Py_tp_clear, lru_list_elem_clear},
{0, 0}
};

static PyType_Spec lru_list_elem_type_spec = {
.name = "functools._lru_list_elem",
.basicsize = sizeof(lru_list_elem),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_HAVE_GC),
.slots = lru_list_elem_type_slots
};

Expand Down Expand Up @@ -1045,8 +1066,8 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
self->root.next == &self->root)
{
/* Cache is not full, so put the result in a new link */
link = (lru_list_elem *)PyObject_New(lru_list_elem,
self->lru_list_elem_type);
link = (lru_list_elem *)PyObject_GC_New(lru_list_elem,
self->lru_list_elem_type);
if (link == NULL) {
Py_DECREF(key);
Py_DECREF(result);
Expand All @@ -1056,6 +1077,7 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
link->hash = hash;
link->key = key;
link->result = result;
PyObject_GC_Track(link);
/* What is really needed here is a SetItem variant with a "no clobber"
option. If the __eq__ call triggers a reentrant call that adds
this same key, then this setitem call will update the cache dict
Expand Down Expand Up @@ -1345,9 +1367,7 @@ 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->key);
Py_VISIT(link->result);
Py_VISIT(Py_TYPE(link));
Py_VISIT(link);
link = next;
}
Py_VISIT(self->cache);
Expand Down