Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 59400) +++ Python/pythonrun.c (working copy) @@ -501,6 +501,9 @@ /* Cleanup Unicode implementation */ _PyUnicode_Fini(); + + /* Report counts of attribute cache hits/misses (if enabled) */ + PyType_Fini(); /* reset file system default encoding */ if (!Py_HasFileSystemDefaultEncoding) { Index: Include/object.h =================================================================== --- Include/object.h (revision 59400) +++ Include/object.h (working copy) @@ -373,6 +373,9 @@ PyObject *tp_subclasses; PyObject *tp_weaklist; destructor tp_del; + /* Unique cache ID per type, assigned when bases change (see + mro_internal) */ + PY_LONG_LONG tp_cache_id; #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ Index: Include/pythonrun.h =================================================================== --- Include/pythonrun.h (revision 59400) +++ Include/pythonrun.h (working copy) @@ -141,6 +141,7 @@ PyAPI_FUNC(void) PyBytes_Fini(void); PyAPI_FUNC(void) PyFloat_Fini(void); PyAPI_FUNC(void) PyOS_FiniInterrupts(void); +PyAPI_FUNC(void) PyType_Fini(void); /* Stuff with no proper home (yet) */ PyAPI_FUNC(char *) PyOS_Readline(FILE *, FILE *, char *); Index: fastattr_test_py3k.py =================================================================== --- fastattr_test_py3k.py (revision 0) +++ fastattr_test_py3k.py (revision 0) @@ -0,0 +1,265 @@ +#!/usr/bin/python + +import timeit, random, time, sys + +MULTIPLIER = 1 + + +class A(object): + def __init__(self, *args): + pass + +class B(A): pass + +class C(B): pass + +class D(C): pass + +class E(D): pass + +class F(E): pass + +class G(F): pass + +class H(G): + def __init__(self): + pass + +class I(H): pass + + +def test_init(tmp): + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__; tmp.__init__ + + +def test_class(tmp): + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__; tmp.__class__ + + +def test_has_init(tmp): + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + hasattr(tmp, '__init__'); hasattr(tmp, '__init__') + + +def test_has_class(tmp): + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + hasattr(tmp, '__class__'); hasattr(tmp, '__class__') + + +def test_has_intit(tmp): + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + hasattr(tmp, '__intit__'); hasattr(tmp, '__intit__') + + +def test_has_klass(tmp): + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + hasattr(tmp, '__klass__'); hasattr(tmp, '__klass__') + + +testclasses = [list, dict, tuple, A, B, C, D, E, F, G, H, I] +list = list +dict = dict +tuple = tuple +list_inst = list() +dict_inst = dict() +tuple_inst = tuple() +A_inst = A() +B_inst = B() +C_inst = C() +D_inst = D() +E_inst = E() +F_inst = F() +G_inst = G() +H_inst = H() +I_inst = I() + +if __name__ == '__main__': + print('class class.__class__ class.__init__' + + ' class().__class__ class().__init__') + for cls in testclasses: + name = cls.__name__ + print(name, end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_class(%s)' % name, + 'from __main__ import test_class, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_init(%s)' % name, + 'from __main__ import test_init, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_class(%s_inst)' % name, + 'from __main__ import test_class, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_init(%s_inst)' % name, + 'from __main__ import test_init, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print() + print() + + print('class hasattr(class,"__class__") hasattr(class,"__init__")' + + ' hasattr(class(),"__class__") hasattr(class(),"__init__")') + for cls in testclasses: + name = cls.__name__ + print(name, end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_class(%s)' % name, + 'from __main__ import test_has_class, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_init(%s)' % name, + 'from __main__ import test_has_init, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_class(%s_inst)' % name, + 'from __main__ import test_has_class, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_init(%s_inst)' % name, + 'from __main__ import test_has_init, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print() + print() + + print('class hasattr(class,"__klass__") hasattr(class,"__intit__")' + + ' hasattr(class(),"__klass__") hasattr(class(),"__intit__")') + for cls in testclasses: + name = cls.__name__ + print(name, end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_klass(%s)' % name, + 'from __main__ import test_has_klass, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_intit(%s)' % name, + 'from __main__ import test_has_intit, %s' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_klass(%s_inst)' % name, + 'from __main__ import test_has_klass, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print(timeit.Timer('test_has_intit(%s_inst)' % name, + 'from __main__ import test_has_intit, %s_inst' % name) + .timeit(number=100000 * MULTIPLIER), end=' ') + sys.stdout.flush() + + print() + print() Property changes on: fastattr_test_py3k.py ___________________________________________________________________ Name: svn:executable + * Index: Objects/object.c =================================================================== --- Objects/object.c (revision 59400) +++ Objects/object.c (working copy) @@ -947,6 +947,7 @@ goto done; } +#if 0 /* Inline _PyType_Lookup */ { Py_ssize_t i, n; @@ -967,6 +968,9 @@ break; } } +#else + descr = _PyType_Lookup(tp, name); +#endif /* 0 */ Py_XINCREF(descr); Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 59400) +++ Objects/typeobject.c (working copy) @@ -6,6 +6,97 @@ #include +/* +Attribute Lookup Caching + +This is implemented with an attribute cache that is dirt-simple for speed +and global for good spatial locality. Per-type caches have been tried and +didn't perform as well as this, and anything using standard dicts is +almost guaranteed to be slower. + +Types now have a tp_cache_id, which uniquely identifies them to the global +cache. This, along with the unicode hash, are combined to make an index +into the cache. The cache is not fully associative: an entry will be kicked +out if another attribute wants the same slot. + +Attributes can be set on a type only via setattr, which updates any cache +entries for that attribute using the update_subclasses mechanism. +*/ + +/* Note: defining this will cause some regression tests to fail */ +/*#define ATTRCACHE_COUNTS*/ + +#ifdef ATTRCACHE_COUNTS +static int attrcache_hits = 0; +static int attrcache_misses = 0; +#endif + +/* This should be used on lookups that are expected to be performed only +once or infrequently (i.e. are expected to miss), such as slots lookups */ +static PyObject *_PyType_LookupInternal(PyTypeObject *type, PyObject *name); + +/* Cache is 2**ATTRCACHE_SIZE_EXP entries */ +#define ATTRCACHE_SIZE_EXP 13 /* 8192 entries */ +/* Too few entries (originally 1024) tends to slow down unrelated code +paths such as long/long and float/float compares - don't know why - +but this is probably a picky, architecture-dependent setting */ + +/* Compute an index into the cache for a type and name hash. Low-order bits +of tp_cache_id are multiplied to spread entries around. High-order bits +are retained as the index for the same reason. */ +#define ATTRCACHE_INDEX(type, name_hash) \ + (((unsigned int)(type)->tp_cache_id * (unsigned int)(name_hash)) \ + >> (8*sizeof(unsigned int) - ATTRCACHE_SIZE_EXP)) + +typedef struct { + PY_LONG_LONG tp_cache_id; /* from type->tp_cache_id */ + PyObject *name; /* reference to unicode object or NULL */ + PyObject *value; /* borrowed reference to attribute value or NULL */ +} attrcache_entry; + +/* The cache itself */ +static attrcache_entry attribute_cache[1 << ATTRCACHE_SIZE_EXP]; +/* The next new type or type that has its MRO changed gets this number as +its tp_cache_id, then increments it. It would take at least a million +years to overflow it at current processing speeds. */ +static PY_LONG_LONG next_cache_id = 1; /* start nonzero */ + +/* Bundle name and value for update_subclasses callback */ +typedef struct { + PyObject *name; + PyObject *value; +} namevalue; + +/* An update_subclasses callback, called on type_setattro. Looks for a cache +entry matching the type/name, and updates the value if it finds one. Returns +0 on success, -1 on failure to hash the unicode object. + +update_subclasses stops recursing through subclasses when it finds a type +with the name in its tp_dict. This is exactly right for us, since the +subclass attribute shadows the one that was just set. */ +int +type_attrcache_callback(PyTypeObject *type, void *data) +{ + register namevalue nv = *(namevalue *)data; + register attrcache_entry *ep; + long hash; + + assert(PyUnicode_CheckExact(nv.name)); + + if ((hash = ((PyUnicodeObject *)nv.name)->hash) == -1) { + hash = PyObject_Hash(nv.name); + if (hash == -1) /* Needed? Unicode hash can't fail */ + return -1; + } + ep = &attribute_cache[ATTRCACHE_INDEX(type, hash)]; + if (ep->tp_cache_id == type->tp_cache_id && + ep->name == nv.name) { + ep->value = nv.value; /* borrowed */ + } + + return 0; +} + static PyMemberDef type_members[] = { {"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY}, {"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY}, @@ -872,7 +963,7 @@ if (*attrobj == NULL) return NULL; } - res = _PyType_Lookup(Py_Type(self), *attrobj); + res = _PyType_LookupInternal(Py_Type(self), *attrobj); if (res != NULL) { descrgetfunc f; if ((f = Py_Type(res)->tp_descr_get) == NULL) @@ -1299,6 +1390,7 @@ } } type->tp_mro = tuple; + type->tp_cache_id = next_cache_id++; return 0; } @@ -1428,7 +1520,7 @@ if (dict_str == NULL) return NULL; } - descr = _PyType_Lookup(type, dict_str); + descr = _PyType_LookupInternal(type, dict_str); if (descr == NULL || !PyDescr_IsData(descr)) return NULL; @@ -2021,8 +2113,8 @@ /* Internal API to look for a name through the MRO. This returns a borrowed reference, and doesn't set an exception! */ -PyObject * -_PyType_Lookup(PyTypeObject *type, PyObject *name) +static PyObject * +_PyType_LookupInternal(PyTypeObject *type, PyObject *name) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; @@ -2050,6 +2142,45 @@ return NULL; } +/* Looks up an attribute in the MRO. Returns NULL on failure or not-found, +never sets an exception, otherwise returns a borrowed reference. This +caches lookups, including failed ones. Use _PyType_LookupInternal instead +for lookups that are done once or infrequently, such as slots. */ +PyObject * +_PyType_Lookup(PyTypeObject *type, register PyObject *name) +{ + register attrcache_entry *ep; + long hash; + + if (!PyUnicode_CheckExact(name)) + return _PyType_LookupInternal(type, name); + + if ((hash = ((PyUnicodeObject *)name)->hash) == -1) { + hash = PyObject_Hash(name); + if (hash == -1) /* Needed? Unicode hash can't fail */ + return NULL; + } + ep = &attribute_cache[ATTRCACHE_INDEX(type, hash)]; + if (ep->tp_cache_id == type->tp_cache_id && + ep->name == name) { +#ifdef ATTRCACHE_COUNTS + attrcache_hits++; +#endif + assert(ep->value == _PyType_LookupInternal(type, name)); + return ep->value; + } +#ifdef ATTRCACHE_COUNTS + attrcache_misses++; +#endif + + ep->tp_cache_id = type->tp_cache_id; + Py_XDECREF(ep->name); + Py_INCREF(name); + ep->name = name; + ep->value = _PyType_LookupInternal(type, name); /* borrowed */ + return ep->value; +} + /* This is similar to PyObject_GenericGetAttr(), but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ static PyObject * @@ -2137,10 +2268,14 @@ 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 (PyUnicode_CheckExact(name)) { + namevalue nv; + nv.name = name; + nv.value = value; + if (update_subclasses(type, name, + type_attrcache_callback, &nv) < 0) + return -1; + } if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); @@ -4295,7 +4430,7 @@ if (getitem_str == NULL) return NULL; } - func = _PyType_Lookup(Py_Type(self), getitem_str); + func = _PyType_LookupInternal(Py_Type(self), getitem_str); if (func != NULL) { if ((f = Py_Type(func)->tp_descr_get) == NULL) Py_INCREF(func); @@ -4700,13 +4835,13 @@ if (getattribute_str == NULL) return NULL; } - getattr = _PyType_Lookup(tp, getattr_str); + getattr = _PyType_LookupInternal(tp, getattr_str); if (getattr == NULL) { /* No __getattr__ hook: use a simpler dispatcher */ tp->tp_getattro = slot_tp_getattro; return slot_tp_getattro(self, name); } - getattribute = _PyType_Lookup(tp, getattribute_str); + getattribute = _PyType_LookupInternal(tp, getattribute_str); if (getattribute == NULL || (Py_Type(getattribute) == &PyWrapperDescr_Type && ((PyWrapperDescrObject *)getattribute)->d_wrapped == @@ -4832,7 +4967,7 @@ if (get_str == NULL) return NULL; } - get = _PyType_Lookup(tp, get_str); + get = _PyType_LookupInternal(tp, get_str); if (get == NULL) { /* Avoid further slowdowns */ if (tp->tp_descr_get == slot_tp_descr_get) @@ -5311,7 +5446,7 @@ return p; } do { - descr = _PyType_Lookup(type, p->name_strobj); + descr = _PyType_LookupInternal(type, p->name_strobj); if (descr == NULL) continue; if (Py_Type(descr) == &PyWrapperDescr_Type) { @@ -5943,3 +6078,13 @@ PyType_GenericNew, /* tp_new */ PyObject_GC_Del, /* tp_free */ }; + +void +PyType_Fini(void) +{ +#ifdef ATTRCACHE_COUNTS + printf("Attribute cache hits: %d, misses: %d, percent hit: %.2f\n", + attrcache_hits, attrcache_misses, + 100.0*attrcache_hits/(attrcache_hits+attrcache_misses)); +#endif +}