Skip to content

Commit bdbadb9

Browse files
gh-94673: Recover Weaklist Lookup Performance (gh-95544)
gh-95302 seems to have introduced a small performance regression. Here we make some minor changes to recover that lost performance.
1 parent 60f54d9 commit bdbadb9

File tree

3 files changed

+47
-7
lines changed

3 files changed

+47
-7
lines changed

Include/internal/pycore_object.h

+33
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,16 @@ extern void _Py_PrintReferences(FILE *);
217217
extern void _Py_PrintReferenceAddresses(FILE *);
218218
#endif
219219

220+
221+
/* Return the *address* of the object's weaklist. The address may be
222+
* dereferenced to get the current head of the weaklist. This is useful
223+
* for iterating over the linked list of weakrefs, especially when the
224+
* list is being modified externally (e.g. refs getting removed).
225+
*
226+
* The returned pointer should not be used to change the head of the list
227+
* nor should it be used to add, remove, or swap any refs in the list.
228+
* That is the sole responsibility of the code in weakrefobject.c.
229+
*/
220230
static inline PyObject **
221231
_PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)
222232
{
@@ -226,10 +236,33 @@ _PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)
226236
(PyTypeObject *)op);
227237
return _PyStaticType_GET_WEAKREFS_LISTPTR(state);
228238
}
239+
// Essentially _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET():
229240
Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset;
230241
return (PyObject **)((char *)op + offset);
231242
}
232243

244+
/* This is a special case of _PyObject_GET_WEAKREFS_LISTPTR().
245+
* Only the most fundamental lookup path is used.
246+
* Consequently, static types should not be used.
247+
*
248+
* For static builtin types the returned pointer will always point
249+
* to a NULL tp_weaklist. This is fine for any deallocation cases,
250+
* since static types are never deallocated and static builtin types
251+
* are only finalized at the end of runtime finalization.
252+
*
253+
* If the weaklist for static types is actually needed then use
254+
* _PyObject_GET_WEAKREFS_LISTPTR().
255+
*/
256+
static inline PyWeakReference **
257+
_PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(PyObject *op)
258+
{
259+
assert(!PyType_Check(op) ||
260+
((PyTypeObject *)op)->tp_flags & Py_TPFLAGS_HEAPTYPE);
261+
Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset;
262+
return (PyWeakReference **)((char *)op + offset);
263+
}
264+
265+
233266
// Fast inlined version of PyObject_IS_GC()
234267
static inline int
235268
_PyObject_IS_GC(PyObject *obj)

Modules/gcmodule.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -794,9 +794,12 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
794794
if (! _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op)))
795795
continue;
796796

797-
/* It supports weakrefs. Does it have any? */
798-
wrlist = (PyWeakReference **)
799-
_PyObject_GET_WEAKREFS_LISTPTR(op);
797+
/* It supports weakrefs. Does it have any?
798+
*
799+
* This is never triggered for static types so we can avoid the
800+
* (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR().
801+
*/
802+
wrlist = _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(op);
800803

801804
/* `op` may have some weakrefs. March over the list, clear
802805
* all the weakrefs, and move the weakrefs with callbacks

Objects/typeobject.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -1505,11 +1505,15 @@ subtype_dealloc(PyObject *self)
15051505
finalizers since they might rely on part of the object
15061506
being finalized that has already been destroyed. */
15071507
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
1508-
/* Modeled after GET_WEAKREFS_LISTPTR() */
1509-
PyWeakReference **list = (PyWeakReference **) \
1510-
_PyObject_GET_WEAKREFS_LISTPTR(self);
1511-
while (*list)
1508+
/* Modeled after GET_WEAKREFS_LISTPTR().
1509+
1510+
This is never triggered for static types so we can avoid the
1511+
(slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR(). */
1512+
PyWeakReference **list = \
1513+
_PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(self);
1514+
while (*list) {
15121515
_PyWeakref_ClearRef(*list);
1516+
}
15131517
}
15141518
}
15151519

0 commit comments

Comments
 (0)