HasTraits instantiation performance degradation from 6.0.0 => 6.1.1 => 6.2.0
- Dominant language
- Python
- Stars
- 462
- Forks
- 90
- PR merge metrics
- No merged PRs in 30d
Description
This was reported to me informally by @dgoeries. He measured performance degradation of `HasTraits` instantiation on a large long-lived codebase when upgrading to Traits 6.1.1.
I've done my best to replicate what the code is doing in a minimally reproducible form.
### Minimal Example
expand
```python
import random
import secrets
import time
from traits.api import (
Dict,
Event,
Float,
HasStrictTraits,
Instance,
Property,
String,
Undefined,
cached_property,
)
class Variable(HasStrictTraits):
# data
attributes = Dict()
value = Undefined
timestamp = Instance(object)
event1 = Event()
event2 = Event()
# convenience properties
attr1 = Property(depends_on='attributes')
attr2 = Property(depends_on='attributes')
attr3 = Property(depends_on='attributes')
attr4 = Property(depends_on='attributes')
attr5 = Property(depends_on='attributes')
@cached_property
def _get_attr1(self):
return self.attributes.get('foo')
@cached_property
def _get_attr2(self):
return self.attributes.get('foo')
@cached_property
def _get_attr3(self):
return self.attributes.get('foo')
@cached_property
def _get_attr4(self):
return self.attributes.get('foo')
@cached_property
def _get_attr5(self):
return self.attributes.get('foo')
class ObjectRoot(Variable):
value = Instance(dict, args=())
name = String()
schema_change = Event()
class NumberVariable(Variable):
value = Float()
class StringVariable(Variable):
value = String()
def create_random_schema(name="root", level=2):
"""Create a big and deeply nested object description."""
VARS_PER_LEVEL = 5
schema = {"name": name}
level_schema = schema.setdefault("variables", {})
for idx in range(VARS_PER_LEVEL):
name_extra = f"{level}_{secrets.token_hex(2)}"
if level > 0:
name = f"subobj_{name_extra}"
level_schema[name] = create_random_schema(name=name, level=level - 1)
else:
name = f"var_{name_extra}"
vartype = "str" if idx % 2 == 0 else "num"
varval = secrets.token_hex(4) if vartype == "str" else random.random() * 100
level_schema[name] = {"name": name, "value": varval, "type": vartype}
return schema
def generate_from_schema(schema):
"""Recursively generate an instance from a schema describing it."""
def _gen_node(value):
if "variables" in value:
return generate_from_schema(value)
valtype = value["type"]
if valtype == "num":
return NumberVariable(value=value["value"])
elif valtype == "str":
return StringVariable(value=value["value"])
raise ValueError(f"{value}")
obj = ObjectRoot(name=schema["name"])
for key, value in schema["variables"].items():
obj.value[key] = _gen_node(value)
obj.schema_change = True
return obj
def summarize_schema(schema, level=0):
"""Show a condensed representation of the object we're generating."""
indent = " " * (level + 1) * 2
if level == 0:
print("Root:")
for key, value in schema["variables"].items():
if "variables" in value:
print(f"{indent}{key}:")
summarize_schema(value, level + 1)
else:
print(f"{indent}{key}: {value['type']}")
def main():
COUNT = 9
schema = create_random_schema()
# summarize_schema(schema)
accum_time = 0
for _ in range(COUNT):
start = time.perf_counter()
generate_from_schema(schema)
accum_time += time.perf_counter() - start
print("Total time:", accum_time)
print("Time per instantiation:", accum_time / COUNT)
if __name__ == "__main__":
main()
```
### Timings
Traits 6.2.0
```
Total time: 1.1325114750652574
Time per instantiation: 0.12583460834058416
```
Traits 6.1.1
```
Total time: 0.8695192660088651
Time per instantiation: 0.09661325177876279
```
Traits 6.0.0
```
Total time: 0.7810043709760066
Time per instantiation: 0.08677826344177851
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.