""" A micro benchmark for attribute lookup in a class hierarcy. Note: CPython contains an attribute lookup cache (look for MCACHE in typeobject.c) which results into fast lookup in the general case but defeats the purpose of this benchmark. Therefore the benchmark is run a second time in a way that circumvents the cache. """ import timeit def make_classes(depth): assert depth > 0 class root: def method(self): return 42 current = root for r in range(depth): current = type("cls%d"%(r,), (current,), {}) return current class mystr (str): pass def perform_benchmark(description, class_builder, name): cls1 = class_builder(1) cls10 = class_builder(10) cls100 = class_builder(100) assert getattr(cls1(), name)() assert getattr(cls10(), name)() assert getattr(cls100(), name)() print() print(description) t = timeit.Timer('getattr(obj, name)', setup='obj=cls1()', globals=locals()) print("depth 1: %.5s" % t.timeit() ) t = timeit.Timer('getattr(obj, name)', setup='obj=cls10()', globals=locals()) print("depth 10: %.5s" % t.timeit() ) t = timeit.Timer('getattr(obj, name)', setup='obj=cls100()', globals=locals()) print("depth 100: %.5s" % t.timeit() ) print() print(description + " using super") t = timeit.Timer('getattr(obj, name)', setup='obj=cls1(); obj=super(cls1, obj)', globals=locals()) print("depth 1: %.5s" % t.timeit() ) t = timeit.Timer('getattr(obj, name)', setup='obj=cls10(); obj=super(cls10, obj)', globals=locals()) print("depth 10: %.5s" % t.timeit() ) t = timeit.Timer('getattr(obj, name)', setup='obj=cls100(); obj=super(cls100, obj)', globals=locals()) print("depth 100: %.5s" % t.timeit() ) perform_benchmark("Regular lookup", make_classes, "method") perform_benchmark("Regular lookup without MCACHE", make_classes, mystr("method")) if hasattr(type, '__getdescriptor__'): from _testcapi import TypeWithDefaultGetDescriptor class MetaClass (type): def __getdescriptor__(cls, name): try: return cls.__dict__[name] except KeyError: raise AttributeError(name) def make_classes_descr(depth): assert depth > 0 class root (metaclass=MetaClass): def method(self): return 42 current = root for r in range(depth): current = type("cls%d"%(r,), (current,), {}) return current def make_classes_descr_C(depth): assert depth > 0 class root (metaclass=TypeWithDefaultGetDescriptor): def method(self): return 42 current = root for r in range(depth): current = type("cls%d"%(r,), (current,), {}) return current perform_benchmark("__getdescriptor__ in C", make_classes_descr_C, "method") perform_benchmark("__getdescriptor__ in C without MCACHE", make_classes_descr_C, mystr("method")) perform_benchmark("__getdescriptor__ in Python", make_classes_descr, "method") perform_benchmark("__getdescriptor__ in Python without MCACHE", make_classes_descr, mystr("method"))