Index: Include/object.h =================================================================== --- Include/object.h (revision 53910) +++ Include/object.h (working copy) @@ -317,6 +317,8 @@ PyObject *tp_weaklist; destructor tp_del; + unsigned int tp_version_tag; + #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ int tp_allocs; @@ -495,6 +497,9 @@ #define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0 #endif +#define Py_TPFLAGS_HAVE_VERSION_TAG (1L<<16) +#define Py_TPFLAGS_VALID_VERSION_TAG (1L<<17) + #define Py_TPFLAGS_DEFAULT ( \ Py_TPFLAGS_HAVE_GETCHARBUFFER | \ Py_TPFLAGS_HAVE_SEQUENCE_IN | \ @@ -504,6 +509,7 @@ Py_TPFLAGS_HAVE_ITER | \ Py_TPFLAGS_HAVE_CLASS | \ Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | \ + Py_TPFLAGS_HAVE_VERSION_TAG | \ 0) #define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0) Index: Objects/object.c =================================================================== --- Objects/object.c (revision 53910) +++ Objects/object.c (working copy) @@ -1236,6 +1236,7 @@ goto done; } +#if 0 /* Inline _PyType_Lookup */ { int i, n; @@ -1260,6 +1261,9 @@ break; } } +#else + descr = _PyType_Lookup(tp, name); +#endif Py_XINCREF(descr); Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 53910) +++ Objects/typeobject.c (working copy) @@ -18,6 +18,42 @@ {0} }; +static void +type_modified(PyTypeObject *type) +{ + /* Invariants: + * + * - Py_TPFLAGS_VALID_VERSION_TAG is never set if + * Py_TPFLAGS_HAVE_VERSION_TAG is not set (e.g. on type + * objects coming from non-recompiled extension modules) + * + * - before Py_TPFLAGS_VALID_VERSION_TAG can be set on a type, + * it must first be set on all super types. + * + * This function clears the Py_TPFLAGS_VALID_VERSION_TAG of a + * type (so it must first clear it on all subclasses). The + * tp_version_tag value is meaningless unless this flag is set. + * We don't assign new version tags eagerly, but only as + * needed. + */ + PyObject *raw, *ref; + int i, n; + if (!(type->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG)) + return; + raw = type->tp_subclasses; + if (raw != NULL) { + n = PyList_GET_SIZE(raw); + for (i = 0; i < n; i++) { + ref = PyList_GET_ITEM(raw, i); + ref = PyWeakref_GET_OBJECT(ref); + if (ref != Py_None) { + type_modified((PyTypeObject *)ref); + } + } + } + type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; +} + static PyObject * type_name(PyTypeObject *type, void *context) { @@ -117,6 +153,7 @@ return -1; } + type_modified(type); return PyDict_SetItemString(type->tp_dict, "__module__", value); } @@ -1291,6 +1328,7 @@ mro_internal(PyTypeObject *type) { PyObject *mro, *result, *tuple; + int i, len; if (type->ob_type == &PyType_Type) { result = mro_implementation(type); @@ -1308,6 +1346,33 @@ tuple = PySequence_Tuple(result); Py_DECREF(result); type->tp_mro = tuple; + + type_modified(type); + len = PyTuple_GET_SIZE(tuple); + for (i = 0; i < len; i++) { + PyObject *cls = PyTuple_GET_ITEM(tuple, i); + if (!PyType_Check(cls) || + !PyType_IsSubtype(type, (PyTypeObject *)cls)) { + /* no support for old-style classes, neither + directly nor if they appear in the MRO of a + new-style class. No support either for + custom MROs that include types that are + not officially super types. */ + type->tp_flags &= ~Py_TPFLAGS_HAVE_VERSION_TAG; + break; + } + } + tuple = type->tp_bases; + len = PyTuple_GET_SIZE(tuple); + for (i = 0; i < len; i++) { + PyObject *b = PyTuple_GET_ITEM(tuple, i); + if (!PyType_Check(b)) { + /* corner case: the old-style super class might + have been hidden from the custom MRO */ + type->tp_flags &= ~Py_TPFLAGS_HAVE_VERSION_TAG; + break; + } + } return 0; } @@ -1967,6 +2032,64 @@ return (PyObject *)type; } +#define METHOD_CACHE_SIZE_EXP 10 +#define MCACHE_HASH(version, name_hash) \ + (((unsigned int)(version) * (unsigned int)(name_hash)) \ + >> (8*sizeof(unsigned int) - METHOD_CACHE_SIZE_EXP)) + +struct method_cache_entry { + int version; + PyObject *name; /* reference to exactly a str or None */ + PyObject *value; /* borrowed */ +}; + +static struct method_cache_entry method_cache[1 << METHOD_CACHE_SIZE_EXP]; +static unsigned int next_version_tag = 0; + +static int +assign_version_tag(PyTypeObject *type) +{ + /* Ensure that the tp_version_tag is valid and set + Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this + must first be done on all super classes. Return -1 if this + cannot be done. + */ + int i, n; + PyObject *bases; + + if (type->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG) + return 0; + if (!(type->tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG)) + return -1; + if (!(type->tp_flags & Py_TPFLAGS_READY)) + return -1; + + type->tp_version_tag = next_version_tag++; + /* for stress-testing: next_version_tag &= 0xFF; */ + if (type->tp_version_tag == 0) { + /* wrap-around or just starting Python - clear the whole + cache by filling it with references to Py_None */ + for (i = 0; i < (1 << METHOD_CACHE_SIZE_EXP); i++) { + Py_XDECREF(method_cache[i].name); + method_cache[i].name = Py_None; + Py_INCREF(Py_None); + } + /* mark all version tags as invalid */ + type_modified(&PyBaseObject_Type); + return -1; + } + bases = type->tp_bases; + n = PyTuple_GET_SIZE(bases); + for (i = 0; i < n; i++) { + PyObject *b = PyTuple_GET_ITEM(bases, i); + assert(PyType_Check(b)); + if (assign_version_tag((PyTypeObject *)b) < 0) + return -1; + } + type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG; + return 0; +} + /* Internal API to look for a name through the MRO. This returns a borrowed reference, and doesn't set an exception! */ PyObject * @@ -1974,7 +2097,25 @@ { int i, n; PyObject *mro, *res, *base, *dict; + int name_hash; + unsigned int version_tag; + unsigned int h; + assert(PyString_Check(name)); + name_hash = ((PyStringObject *)name)->ob_shash; + /* name_hash should not be -1, but even if it is, then the caching + code below is not incorrect - just not helping much. */ + + if (type->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG) { + /* fast path */ + version_tag = type->tp_version_tag; + h = MCACHE_HASH(version_tag, name_hash); + if (method_cache[h].version == version_tag && + method_cache[h].name == name) { + return method_cache[h].value; + } + } + /* Look in tp_dict of types in MRO */ mro = type->tp_mro; @@ -1984,6 +2125,7 @@ if (mro == NULL) return NULL; + res = NULL; assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { @@ -1997,9 +2139,26 @@ assert(dict && PyDict_Check(dict)); res = PyDict_GetItem(dict, name); if (res != NULL) - return res; + break; } - return NULL; + + if (assign_version_tag(type) < 0) + return res; + if (!PyString_CheckExact(name)) + return res; + version_tag = type->tp_version_tag; + h = MCACHE_HASH(version_tag, name_hash); + method_cache[h].version = version_tag; + method_cache[h].value = res; /* borrowed */ + Py_INCREF(name); + Py_DECREF(method_cache[h].name); + method_cache[h].name = name; + /* NB. the cache can keep references to the names alive for + longer than they normally would. This might be a problem if + very large strings are used as attribute names. Maybe we + should not cache the result of a lookup for names longer than + some threshold. */ + return res; } /* This is similar to PyObject_GenericGetAttr(), @@ -2089,10 +2248,6 @@ type->tp_name); return -1; } - /* XXX Example of how I expect this to be used... - if (update_subclasses(type, name, invalidate_cache, NULL) < 0) - return -1; - */ if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); @@ -5338,6 +5493,13 @@ slotdef **pp; int offset; + /* Clear the VALID_VERSION flag of 'type' and all its + subclasses. This could possibly be unified with the + update_subclasses() recursion below, but carefully: + they each have their own conditions on which to stop + recursing into subclasses. */ + type_modified(type); + init_slotdefs(); pp = ptrs; for (p = slotdefs; p->name; p++) {