mirror of https://github.com/python/cpython
Patch #1700288: Method cache optimization, by Armin Rigo, ported to
2.6 by Kevin Jacobs.
This commit is contained in:
parent
57fe0f2902
commit
27e26ec418
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
Loading…
Reference in New Issue