Issue #17936: Fix O(n**2) behaviour when adding or removing many subclasses of a given type.

This commit is contained in:
Antoine Pitrou 2013-10-29 21:31:25 +01:00
parent dc6b933d23
commit 84745ab464
2 changed files with 66 additions and 59 deletions

View File

@ -10,6 +10,9 @@ Projected release date: 2013-11-24
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #17936: Fix O(n**2) behaviour when adding or removing many subclasses
of a given type.
- Issue #19428: zipimport now handles errors when reading truncated or invalid - Issue #19428: zipimport now handles errors when reading truncated or invalid
ZIP archive. ZIP archive.

View File

@ -101,16 +101,17 @@ PyType_Modified(PyTypeObject *type)
needed. needed.
*/ */
PyObject *raw, *ref; PyObject *raw, *ref;
Py_ssize_t i, n; Py_ssize_t i;
if (!PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) if (!PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
return; return;
raw = type->tp_subclasses; raw = type->tp_subclasses;
if (raw != NULL) { if (raw != NULL) {
n = PyList_GET_SIZE(raw); assert(PyDict_CheckExact(raw));
for (i = 0; i < n; i++) { i = 0;
ref = PyList_GET_ITEM(raw, i); while (PyDict_Next(raw, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref));
ref = PyWeakref_GET_OBJECT(ref); ref = PyWeakref_GET_OBJECT(ref);
if (ref != Py_None) { if (ref != Py_None) {
PyType_Modified((PyTypeObject *)ref); PyType_Modified((PyTypeObject *)ref);
@ -435,6 +436,7 @@ static int mro_internal(PyTypeObject *);
static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *); static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *);
static int add_subclass(PyTypeObject*, PyTypeObject*); static int add_subclass(PyTypeObject*, PyTypeObject*);
static void remove_subclass(PyTypeObject *, PyTypeObject *); static void remove_subclass(PyTypeObject *, PyTypeObject *);
static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
static void update_all_slots(PyTypeObject *); static void update_all_slots(PyTypeObject *);
typedef int (*update_callback)(PyTypeObject *, void *); typedef int (*update_callback)(PyTypeObject *, void *);
@ -448,15 +450,15 @@ mro_subclasses(PyTypeObject *type, PyObject* temp)
{ {
PyTypeObject *subclass; PyTypeObject *subclass;
PyObject *ref, *subclasses, *old_mro; PyObject *ref, *subclasses, *old_mro;
Py_ssize_t i, n; Py_ssize_t i;
subclasses = type->tp_subclasses; subclasses = type->tp_subclasses;
if (subclasses == NULL) if (subclasses == NULL)
return 0; return 0;
assert(PyList_Check(subclasses)); assert(PyDict_CheckExact(subclasses));
n = PyList_GET_SIZE(subclasses); i = 0;
for (i = 0; i < n; i++) {
ref = PyList_GET_ITEM(subclasses, i); while (PyDict_Next(subclasses, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref)); assert(PyWeakref_CheckRef(ref));
subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref); subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref);
assert(subclass != NULL); assert(subclass != NULL);
@ -575,13 +577,7 @@ type_set_bases(PyTypeObject *type, PyObject *value, void *context)
/* for now, sod that: just remove from all old_bases, /* for now, sod that: just remove from all old_bases,
add to all new_bases */ add to all new_bases */
for (i = PyTuple_GET_SIZE(old_bases) - 1; i >= 0; i--) { remove_all_subclasses(type, old_bases);
ob = PyTuple_GET_ITEM(old_bases, i);
if (PyType_Check(ob)) {
remove_subclass(
(PyTypeObject*)ob, type);
}
}
for (i = PyTuple_GET_SIZE(value) - 1; i >= 0; i--) { for (i = PyTuple_GET_SIZE(value) - 1; i >= 0; i--) {
ob = PyTuple_GET_ITEM(value, i); ob = PyTuple_GET_ITEM(value, i);
@ -2733,10 +2729,14 @@ static void
type_dealloc(PyTypeObject *type) type_dealloc(PyTypeObject *type)
{ {
PyHeapTypeObject *et; PyHeapTypeObject *et;
PyObject *tp, *val, *tb;
/* Assert this is a heap-allocated type object */ /* Assert this is a heap-allocated type object */
assert(type->tp_flags & Py_TPFLAGS_HEAPTYPE); assert(type->tp_flags & Py_TPFLAGS_HEAPTYPE);
_PyObject_GC_UNTRACK(type); _PyObject_GC_UNTRACK(type);
PyErr_Fetch(&tp, &val, &tb);
remove_all_subclasses(type, type->tp_bases);
PyErr_Restore(tp, val, tb);
PyObject_ClearWeakRefs((PyObject *)type); PyObject_ClearWeakRefs((PyObject *)type);
et = (PyHeapTypeObject *)type; et = (PyHeapTypeObject *)type;
Py_XDECREF(type->tp_base); Py_XDECREF(type->tp_base);
@ -2761,7 +2761,7 @@ static PyObject *
type_subclasses(PyTypeObject *type, PyObject *args_ignored) type_subclasses(PyTypeObject *type, PyObject *args_ignored)
{ {
PyObject *list, *raw, *ref; PyObject *list, *raw, *ref;
Py_ssize_t i, n; Py_ssize_t i;
list = PyList_New(0); list = PyList_New(0);
if (list == NULL) if (list == NULL)
@ -2769,10 +2769,9 @@ type_subclasses(PyTypeObject *type, PyObject *args_ignored)
raw = type->tp_subclasses; raw = type->tp_subclasses;
if (raw == NULL) if (raw == NULL)
return list; return list;
assert(PyList_Check(raw)); assert(PyDict_CheckExact(raw));
n = PyList_GET_SIZE(raw); i = 0;
for (i = 0; i < n; i++) { while (PyDict_Next(raw, &i, NULL, &ref)) {
ref = PyList_GET_ITEM(raw, i);
assert(PyWeakref_CheckRef(ref)); assert(PyWeakref_CheckRef(ref));
ref = PyWeakref_GET_OBJECT(ref); ref = PyWeakref_GET_OBJECT(ref);
if (ref != Py_None) { if (ref != Py_None) {
@ -2961,8 +2960,8 @@ type_clear(PyTypeObject *type)
class's dict; the cycle will be broken that way. class's dict; the cycle will be broken that way.
tp_subclasses: tp_subclasses:
A list of weak references can't be part of a cycle; and A dict of weak references can't be part of a cycle; and
lists have their own tp_clear. dicts have their own tp_clear.
slots (in PyHeapTypeObject): slots (in PyHeapTypeObject):
A tuple of strings can't be part of a cycle. A tuple of strings can't be part of a cycle.
@ -4353,51 +4352,57 @@ PyType_Ready(PyTypeObject *type)
static int static int
add_subclass(PyTypeObject *base, PyTypeObject *type) add_subclass(PyTypeObject *base, PyTypeObject *type)
{ {
Py_ssize_t i; int result = -1;
int result; PyObject *dict, *key, *newobj;
PyObject *list, *ref, *newobj;
list = base->tp_subclasses; dict = base->tp_subclasses;
if (list == NULL) { if (dict == NULL) {
base->tp_subclasses = list = PyList_New(0); base->tp_subclasses = dict = PyDict_New();
if (list == NULL) if (dict == NULL)
return -1; return -1;
} }
assert(PyList_Check(list)); assert(PyDict_CheckExact(dict));
key = PyLong_FromVoidPtr((void *) type);
if (key == NULL)
return -1;
newobj = PyWeakref_NewRef((PyObject *)type, NULL); newobj = PyWeakref_NewRef((PyObject *)type, NULL);
if (newobj == NULL) if (newobj != NULL) {
return -1; result = PyDict_SetItem(dict, key, newobj);
i = PyList_GET_SIZE(list);
while (--i >= 0) {
ref = PyList_GET_ITEM(list, i);
assert(PyWeakref_CheckRef(ref));
if (PyWeakref_GET_OBJECT(ref) == Py_None)
return PyList_SetItem(list, i, newobj);
}
result = PyList_Append(list, newobj);
Py_DECREF(newobj); Py_DECREF(newobj);
}
Py_DECREF(key);
return result; return result;
} }
static void static void
remove_subclass(PyTypeObject *base, PyTypeObject *type) remove_subclass(PyTypeObject *base, PyTypeObject *type)
{ {
Py_ssize_t i; PyObject *dict, *key;
PyObject *list, *ref;
list = base->tp_subclasses; dict = base->tp_subclasses;
if (list == NULL) { if (dict == NULL) {
return; return;
} }
assert(PyList_Check(list)); assert(PyDict_CheckExact(dict));
i = PyList_GET_SIZE(list); key = PyLong_FromVoidPtr((void *) type);
while (--i >= 0) { if (key == NULL || PyDict_DelItem(dict, key)) {
ref = PyList_GET_ITEM(list, i); /* This can happen if the type initialization errored out before
assert(PyWeakref_CheckRef(ref)); the base subclasses were updated (e.g. a non-str __qualname__
if (PyWeakref_GET_OBJECT(ref) == (PyObject*)type) { was passed in the type dict). */
/* this can't fail, right? */ PyErr_Clear();
PySequence_DelItem(list, i); }
return; Py_XDECREF(key);
}
static void
remove_all_subclasses(PyTypeObject *type, PyObject *bases)
{
if (bases) {
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(bases); i++) {
PyObject *base = PyTuple_GET_ITEM(bases, i);
if (PyType_Check(base))
remove_subclass((PyTypeObject*) base, type);
} }
} }
} }
@ -6173,15 +6178,14 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *name,
{ {
PyTypeObject *subclass; PyTypeObject *subclass;
PyObject *ref, *subclasses, *dict; PyObject *ref, *subclasses, *dict;
Py_ssize_t i, n; Py_ssize_t i;
subclasses = type->tp_subclasses; subclasses = type->tp_subclasses;
if (subclasses == NULL) if (subclasses == NULL)
return 0; return 0;
assert(PyList_Check(subclasses)); assert(PyDict_CheckExact(subclasses));
n = PyList_GET_SIZE(subclasses); i = 0;
for (i = 0; i < n; i++) { while (PyDict_Next(subclasses, &i, NULL, &ref)) {
ref = PyList_GET_ITEM(subclasses, i);
assert(PyWeakref_CheckRef(ref)); assert(PyWeakref_CheckRef(ref));
subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref); subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref);
assert(subclass != NULL); assert(subclass != NULL);