Patch #1700288: Method cache optimization, by Armin Rigo, ported to

2.6 by Kevin Jacobs.
This commit is contained in:
Georg Brandl 2008-01-12 13:47:57 +00:00
parent 57fe0f2902
commit 27e26ec418
4 changed files with 219 additions and 6 deletions

View File

@ -345,6 +345,9 @@ typedef struct _typeobject {
PyObject *tp_weaklist; PyObject *tp_weaklist;
destructor tp_del; destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
#ifdef COUNT_ALLOCS #ifdef COUNT_ALLOCS
/* these must be last and never explicitly initialized */ /* these must be last and never explicitly initialized */
Py_ssize_t tp_allocs; Py_ssize_t tp_allocs;
@ -529,6 +532,10 @@ given type object has a specified feature.
/* Objects support nb_index in PyNumberMethods */ /* Objects support nb_index in PyNumberMethods */
#define Py_TPFLAGS_HAVE_INDEX (1L<<17) #define Py_TPFLAGS_HAVE_INDEX (1L<<17)
/* Objects support type attribute cache */
#define Py_TPFLAGS_HAVE_VERSION_TAG (1L<<18)
#define Py_TPFLAGS_VALID_VERSION_TAG (1L<<19)
/* These flags are used to determine if a type is a subclass. */ /* These flags are used to determine if a type is a subclass. */
#define Py_TPFLAGS_INT_SUBCLASS (1L<<23) #define Py_TPFLAGS_INT_SUBCLASS (1L<<23)
#define Py_TPFLAGS_LONG_SUBCLASS (1L<<24) #define Py_TPFLAGS_LONG_SUBCLASS (1L<<24)
@ -550,6 +557,7 @@ given type object has a specified feature.
Py_TPFLAGS_HAVE_CLASS | \ Py_TPFLAGS_HAVE_CLASS | \
Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | \ Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | \
Py_TPFLAGS_HAVE_INDEX | \ Py_TPFLAGS_HAVE_INDEX | \
Py_TPFLAGS_HAVE_VERSION_TAG | \
0) 0)
#define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0) #define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0)

View File

@ -12,6 +12,9 @@ What's New in Python 2.6 alpha 1?
Core and builtins Core and builtins
----------------- -----------------
- Patch #1700288: added a type attribute cache that caches method accesses,
resulting in speedups in heavily object-oriented code.
- Bug #1776: __import__() no longer accepts filenames on any platform. - Bug #1776: __import__() no longer accepts filenames on any platform.
The first parameter to __import__() must be a valid module name. The first parameter to __import__() must be a valid module name.

View File

@ -1287,6 +1287,7 @@ PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
goto done; goto done;
} }
#if 0 /* XXX this is not quite _PyType_Lookup anymore */
/* Inline _PyType_Lookup */ /* Inline _PyType_Lookup */
{ {
Py_ssize_t i, n; Py_ssize_t i, n;
@ -1311,6 +1312,9 @@ PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
break; break;
} }
} }
#else
descr = _PyType_Lookup(tp, name);
#endif
Py_XINCREF(descr); Py_XINCREF(descr);

View File

@ -5,6 +5,171 @@
#include <ctype.h> #include <ctype.h>
/* Support type attribute cache */
/* The cache can keep references to the names alive for longer than
they normally would. This is why the maximum size is limited to
MCACHE_MAX_ATTR_SIZE, since it might be a problem if very large
strings are used as attribute names. */
#define MCACHE_MAX_ATTR_SIZE 100
#define MCACHE_SIZE_EXP 10
#define MCACHE_HASH(version, name_hash) \
(((unsigned int)(version) * (unsigned int)(name_hash)) \
>> (8*sizeof(unsigned int) - MCACHE_SIZE_EXP))
#define MCACHE_HASH_METHOD(type, name) \
MCACHE_HASH((type)->tp_version_tag, \
((PyStringObject *)(name))->ob_shash)
#define MCACHE_CACHEABLE_NAME(name) \
PyString_CheckExact(name) && \
PyString_GET_SIZE(name) <= MCACHE_MAX_ATTR_SIZE
struct method_cache_entry {
unsigned int version;
PyObject *name; /* reference to exactly a str or None */
PyObject *value; /* borrowed */
};
static struct method_cache_entry method_cache[1 << MCACHE_SIZE_EXP];
static unsigned int next_version_tag = 0;
static void
type_modified(PyTypeObject *type)
{
/* Invalidate any cached data for the specified type and all
subclasses. This function is called after the base
classes, mro, or attributes of the type are altered.
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;
Py_ssize_t i, n;
if(!PyType_HasFeature(type, 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 void
type_mro_modified(PyTypeObject *type, PyObject *bases) {
/*
Check that all base classes or elements of the mro of type are
able to be cached. This function is called after the base
classes or mro of the type are altered.
Unset HAVE_VERSION_TAG and VALID_VERSION_TAG if the type
inherits from an old-style class, either directly or if it
appears in the MRO of a new-style class. No support either for
custom MROs that include types that are not officially super
types.
Called from mro_internal, which will subsequently be called on
each subclass when their mro is recursively updated.
*/
Py_ssize_t i, n;
int clear = 0;
if(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_VERSION_TAG))
return;
n = PyTuple_GET_SIZE(bases);
for (i = 0; i < n; i++) {
PyObject *b = PyTuple_GET_ITEM(bases, i);
PyTypeObject *cls;
if (!PyType_Check(b) ) {
clear = 1;
break;
}
cls = (PyTypeObject *)b;
if (!PyType_HasFeature(cls, Py_TPFLAGS_HAVE_VERSION_TAG) ||
!PyType_IsSubtype(type, cls)) {
clear = 1;
break;
}
}
if (clear)
type->tp_flags &= ~(Py_TPFLAGS_HAVE_VERSION_TAG|
Py_TPFLAGS_VALID_VERSION_TAG);
}
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 0 if this
cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG.
*/
Py_ssize_t i, n;
PyObject *bases;
if (PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
return 1;
if (!PyType_HasFeature(type, Py_TPFLAGS_HAVE_VERSION_TAG))
return 0;
if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
return 0;
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 names with references to Py_None.
Values are also set to NULL for added protection, as they
are borrowed reference */
for (i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
method_cache[i].value = NULL;
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))
return 0;
}
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
return 1;
}
static PyMemberDef type_members[] = { static PyMemberDef type_members[] = {
{"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY}, {"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY},
{"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY}, {"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY},
@ -117,6 +282,8 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
return -1; return -1;
} }
type_modified(type);
return PyDict_SetItemString(type->tp_dict, "__module__", value); return PyDict_SetItemString(type->tp_dict, "__module__", value);
} }
@ -1351,6 +1518,14 @@ mro_internal(PyTypeObject *type)
} }
} }
type->tp_mro = tuple; type->tp_mro = tuple;
type_mro_modified(type, type->tp_mro);
/* corner case: the old-style super class might have been hidden
from the custom MRO */
type_mro_modified(type, type->tp_bases);
type_modified(type);
return 0; return 0;
} }
@ -2143,6 +2318,16 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
{ {
Py_ssize_t i, n; Py_ssize_t i, n;
PyObject *mro, *res, *base, *dict; PyObject *mro, *res, *base, *dict;
unsigned int h;
if (MCACHE_CACHEABLE_NAME(name) &&
PyType_HasFeature(type,Py_TPFLAGS_VALID_VERSION_TAG)) {
/* fast path */
h = MCACHE_HASH_METHOD(type, name);
if (method_cache[h].version == type->tp_version_tag &&
method_cache[h].name == name)
return method_cache[h].value;
}
/* Look in tp_dict of types in MRO */ /* Look in tp_dict of types in MRO */
mro = type->tp_mro; mro = type->tp_mro;
@ -2153,6 +2338,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
if (mro == NULL) if (mro == NULL)
return NULL; return NULL;
res = NULL;
assert(PyTuple_Check(mro)); assert(PyTuple_Check(mro));
n = PyTuple_GET_SIZE(mro); n = PyTuple_GET_SIZE(mro);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
@ -2166,9 +2352,18 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
assert(dict && PyDict_Check(dict)); assert(dict && PyDict_Check(dict));
res = PyDict_GetItem(dict, name); res = PyDict_GetItem(dict, name);
if (res != NULL) if (res != NULL)
return res; break;
} }
return NULL;
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
h = MCACHE_HASH_METHOD(type, name);
method_cache[h].version = type->tp_version_tag;
method_cache[h].value = res; /* borrowed */
Py_INCREF(name);
Py_DECREF(method_cache[h].name);
method_cache[h].name = name;
}
return res;
} }
/* This is similar to PyObject_GenericGetAttr(), /* This is similar to PyObject_GenericGetAttr(),
@ -2258,10 +2453,6 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
type->tp_name); type->tp_name);
return -1; 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) if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
return -1; return -1;
return update_slot(type, name); return update_slot(type, name);
@ -5684,6 +5875,13 @@ update_slot(PyTypeObject *type, PyObject *name)
slotdef **pp; slotdef **pp;
int offset; 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(); init_slotdefs();
pp = ptrs; pp = ptrs;
for (p = slotdefs; p->name; p++) { for (p = slotdefs; p->name; p++) {