I am investigating the performance impact of a very broad inheritance setup.
- Start with 260 distinct attribute names, from
a0throughz9. - Create 260 classes with 1 uniquely-named attribute each. Create one class that inherits from those 260 classes.
- Create 130 classes with 2 uniquely-named attributes each. Create one class that inherits from those 130 classes.
- Repeat for 52 classes with 5 attributes each, 26 classes with 10 attributes each, and 1 class with all 260 attributes.
- Create one instance of each of the five classes, and then measure the time to read (and add together) all 260 attributes on each.
Average performance from 2.5M reads, interleaving in different orders.
From260: 2.48
From130: 1.55
From52: 1.22
From26: 1.15
AllInOne: 1.00
These values sort of fit on a linear regression...but they don't. And these relationships hold true across many different runs, of various sizes and test orders.
The values come closer to fitting a second-degree polynomial, or exponential fit...but again, the data does not fit so cleanly as to be obvious.
As I massively increase the number of subclasses, will the performance falloff be linear, or non-linear?
Here's some updated code that samples many different subclass combinations up to 2310:
from time import time
TOTAL_ATTRS = 2310
# Create attribute names "a0000" through "a2309"
attr_names = [f"a{i:04d}" for i in range(TOTAL_ATTRS)]
# Map each attribute to a default value (1..2310)
all_defaults = {name: i + 1 for i, name in enumerate(attr_names)}
# The provided factors of 2310
factors = [1, 2, 3, 5, 6, 7, 10, 11, 14, 15, 21, 22, 30, 33, 35, 42,
55, 66, 70, 77, 105, 110, 154, 165, 210, 231, 330, 385,
462, 770, 1155, 2310]
# Build a dictionary mapping each factor to a composite class.
# For factor f, create f subclasses each with (2310 // f) attributes,
# then create a composite class inheriting from all f subclasses.
composite_classes = {}
for f in factors:
group_size = TOTAL_ATTRS // f
subclasses = []
for i in range(f):
group = attr_names[i * group_size:(i + 1) * group_size]
group_defaults = {name: all_defaults[name] for name in group}
subclass = type(f"Sub_{f}_{i}", (object,), group_defaults)
subclasses.append(subclass)
composite_classes[f] = type(f"From_{f}", tuple(subclasses), {})
iterations = range(0, 1_000)
for n, c in composite_classes.items():
i = c()
t = time()
for _ in iterations:
for a in attr_names:
getattr(i, a)
print(f"Inheriting from {n} subclasses: {time()-t:.3f}s")
and the results, which seem far more linear than polynomial, but which have odd "ledges" in them:



