Skip to content

Commit e2292d4

Browse files
Jammy2211claude
authored andcommitted
perf: cache JSONPriorConfig lookups
path_value_tuples re-sorted the whole flattened config on EVERY lookup (the map was cached, the sort was not) and __call__ then linear-scanned it, with identical queries repeating for every prior of every model construction. Cache the sorted tuples and memoize lookups per instance, including misses (the class-family probe in for_class_and_suffix_path relies on repeated expected misses). Fresh instances per config push = natural invalidation; returned sub-dicts were already aliased across calls. Measured on the aggregator harness: values("model") 8.15 -> 4.60 ms/result (-44%); summaries -25%. Benefits every Model construction. Aggregator-arc deeper follow-up (#129). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 86986be commit e2292d4

2 files changed

Lines changed: 50 additions & 5 deletions

File tree

autoconf/json_prior/config.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ def path_for_class(cls) -> List[str]:
5555
return f"{cls.__module__}.{cls.__name__}".split(".")
5656

5757

58+
# Sentinels for the per-instance lookup cache: a query can legitimately resolve to
59+
# None, and the class-family probe in for_class_and_suffix_path relies on repeated
60+
# expected misses, so both found-None and not-found must be cacheable.
61+
_UNCACHED = object()
62+
_NOT_FOUND = object()
63+
64+
5865
class JSONPriorConfig:
5966
def __init__(self, config_dict: dict, directory=None):
6067
"""
@@ -79,6 +86,8 @@ def __init__(self, config_dict: dict, directory=None):
7986
self.obj = config_dict
8087
self.directory = directory
8188
self._path_value_map = None
89+
self._path_value_tuples = None
90+
self._lookup_cache = {}
8291

8392
@property
8493
def paths(self):
@@ -109,12 +118,18 @@ def path_value_tuples(self) -> List[Tuple[str, object]]:
109118
"""
110119
Tuple pairs matching every possible path to the configuration it points to.
111120
These are ordered by key length with the longest key first.
121+
122+
Cached on the instance — this is consulted for every prior of every model
123+
construction, and re-sorting the flattened configuration per lookup
124+
dominated model deserialization.
112125
"""
113-
return sorted(
114-
list(self.path_value_map.items()),
115-
key=lambda item: len(item[0]),
116-
reverse=True,
117-
)
126+
if self._path_value_tuples is None:
127+
self._path_value_tuples = sorted(
128+
list(self.path_value_map.items()),
129+
key=lambda item: len(item[0]),
130+
reverse=True,
131+
)
132+
return self._path_value_tuples
118133

119134
@classmethod
120135
def from_directory(cls, directory: str) -> "JSONPriorConfig":
@@ -209,9 +224,20 @@ def __call__(self, config_path: List[str]):
209224
If no configuration is found.
210225
"""
211226
key = ".".join(config_path)
227+
cached = self._lookup_cache.get(key, _UNCACHED)
228+
if cached is _NOT_FOUND:
229+
raise KeyError(
230+
f"No configuration was found for the path {config_path}"
231+
+ ("" if self.directory is None else f" ({self.directory})")
232+
)
233+
if cached is not _UNCACHED:
234+
return cached
235+
212236
for path, value in self.path_value_tuples:
213237
if key.endswith(path):
238+
self._lookup_cache[key] = value
214239
return value
240+
self._lookup_cache[key] = _NOT_FOUND
215241
raise KeyError(
216242
f"No configuration was found for the path {config_path}"
217243
+ ("" if self.directory is None else f" ({self.directory})")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
3+
def test_lookup_cache_repeats_and_misses():
4+
"""
5+
Lookups are memoised per instance — including misses, which the class-family
6+
probe in for_class_and_suffix_path performs repeatedly by design.
7+
"""
8+
import pytest
9+
from autoconf.json_prior.config import JSONPriorConfig
10+
11+
config = JSONPriorConfig({"module.Class": {"value": 1}})
12+
13+
assert config(["pkg", "module", "Class"]) == {"value": 1}
14+
assert config(["pkg", "module", "Class"]) == {"value": 1}
15+
16+
with pytest.raises(KeyError):
17+
config(["pkg", "module", "Missing"])
18+
with pytest.raises(KeyError):
19+
config(["pkg", "module", "Missing"])

0 commit comments

Comments
 (0)