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,153 @@ #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. + +Attribute names are compared by identity. Though this will give false +negatives (possibly with computed names) it will never give false positives. +Another way to say it is that name strings are assumed interned but it +doesn't hurt us too much if they're not. +*/ + +/* Defining this will cause regression tests that check stdout 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 */ + +/* It's still not clear whether, on setattr, it's best to +1. Recurse through subclasses, updating cache values, and stopping recursion + before subclasses that shadow the attribute, or +2. Just blindly invalidate the cache entries for every subclass. + +It really depends on real-world usage of class attributes. Define this to +choose approach #2. Default is #1. + +#define ATTRCACHE_SETATTR_INVALIDATES +*/ + +#ifdef ATTRCACHE_SETATTR_INVALIDATES + +/* Called from type_setattro when an attribute is set. Invalidates all +entries in the cache associated with type and all of its subclasses. +Always returns 0. */ +int +attrcache_invalidate_subclasses(PyTypeObject *type) +{ + PyTypeObject *subclass; + PyObject *ref, *subclasses; + Py_ssize_t i, n; + + assert(PyUnicode_CheckExact(name)); + + type->tp_cache_id = next_cache_id++; + + subclasses = type->tp_subclasses; + if (subclasses == NULL) + return 0; + assert(PyList_Check(subclasses)); + n = PyList_GET_SIZE(subclasses); + for (i = 0; i < n; i++) { + ref = PyList_GET_ITEM(subclasses, i); + assert(PyWeakref_CheckRef(ref)); + subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref); + assert(subclass != NULL); + if ((PyObject *)subclass == Py_None) + continue; + assert(PyType_Check(subclass)); + if (attrcache_invalidate_subclasses(subclass) < 0) + return -1; + } + return 0; +} + +#else /* setattr updates */ + +/* 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; +} + +#endif /* #ifdef ATTRCACHE_SETATTR_INVALIDATES */ + static PyMemberDef type_members[] = { {"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY}, {"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY}, @@ -872,7 +1019,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 +1446,7 @@ } } type->tp_mro = tuple; + type->tp_cache_id = next_cache_id++; return 0; } @@ -1428,7 +1576,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 +2169,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 +2198,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 +2324,19 @@ type->tp_name); return -1; } - /* XXX Example of how I expect this to be used... - if (update_subclasses(type, name, invalidate_cache, NULL) < 0) +#ifdef ATTRCACHE_SETATTR_INVALIDATES + if (attrcache_invalidate_subclasses(type) < 0) return -1; - */ +#else + if (PyUnicode_CheckExact(name)) { + namevalue nv; + nv.name = name; + nv.value = value; + if (update_subclasses(type, name, + type_attrcache_callback, &nv) < 0) + return -1; + } +#endif /* #ifdef ATTRCACHE_SETATTR_INVALIDATES */ if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); @@ -4295,7 +4491,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 +4896,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 +5028,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 +5507,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 +6139,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 +}