Fix GC tracking error for instances of mathutils types

Mathutils types were always GC tracked even when it wasn't intended.
Not having to track objects speeds up Python execution.

In an isolated benchmark created to stress test the GC
creating 4-million vectors (re-assigning them 100 times), this gives
an overall ~2.5x speedup, see: P3221.

Details:

Since [0] (which added support for sub-classed mathutils types)
tp_alloc was called which defaults to PyType_GenericAlloc which always
GC tracked the resulting object when Py_TPFLAGS_HAVE_GC was set.

Avoid using PyType_GenericAlloc unless the type is sub-classed,
in that case the object is un-tracked.

Add asserts that the tracked state is as expected before tracking &
un-tracking, to ensure changes to object creation don't cause objects
to be tracked unintentionally.

Also assign the PyTypeObject.tp_is_gc callback so types optionally GC
track objects only do so when an object is referenced.

[0]: fbd9364944
This commit is contained in:
2022-09-28 16:09:12 +10:00
parent ada2b9f6e4
commit 5270ac5ed8
13 changed files with 93 additions and 8 deletions

View File

@@ -697,6 +697,18 @@ int BaseMathObject_clear(BaseMathObject *self)
return 0;
}
/** Only to validate assumptions when debugging. */
#ifndef NDEBUG
static bool BaseMathObject_is_tracked(BaseMathObject *self)
{
PyObject *cb_user = self->cb_user;
self->cb_user = (void *)(uintptr_t)-1;
bool is_tracked = PyObject_GC_IsTracked((PyObject *)self);
self->cb_user = cb_user;
return is_tracked;
}
#endif /* NDEBUG */
void BaseMathObject_dealloc(BaseMathObject *self)
{
/* only free non wrapped */
@@ -705,13 +717,48 @@ void BaseMathObject_dealloc(BaseMathObject *self)
}
if (self->cb_user) {
BLI_assert(BaseMathObject_is_tracked(self) == true);
PyObject_GC_UnTrack(self);
BaseMathObject_clear(self);
}
else if (!BaseMathObject_CheckExact(self)) {
/* Sub-classed types get an extra track (in Pythons internal `subtype_dealloc` function). */
BLI_assert(BaseMathObject_is_tracked(self) == true);
PyObject_GC_UnTrack(self);
BLI_assert(BaseMathObject_is_tracked(self) == false);
}
Py_TYPE(self)->tp_free(self); // PyObject_DEL(self); /* breaks sub-types. */
}
int BaseMathObject_is_gc(BaseMathObject *self)
{
return self->cb_user != NULL;
}
PyObject *_BaseMathObject_new_impl(PyTypeObject *root_type, PyTypeObject *base_type)
{
PyObject *obj;
if (ELEM(base_type, NULL, root_type)) {
obj = _PyObject_GC_New(root_type);
if (obj) {
BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == false);
}
}
else {
/* Calls Generic allocation function which always tracks
* (because `root_type` is flagged for GC). */
obj = base_type->tp_alloc(base_type, 0);
if (obj) {
BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == true);
PyObject_GC_UnTrack(obj);
BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == false);
}
}
return obj;
}
/*----------------------------MODULE INIT-------------------------*/
static struct PyMethodDef M_Mathutils_methods[] = {
{NULL, NULL, 0, NULL},