From 5fd8b8367ec96db75e2e57478fec67984433406c Mon Sep 17 00:00:00 2001 From: song Date: Thu, 17 Sep 2026 15:14:33 +0800 Subject: [PATCH] perf(semantics): parse each Python source once per inventory run python_facts now memoizes on the frozen SourceFile (path, suffix, text), so owner_values and build_inventory share one parse per file instead of two. Measured on the drift smoke: 2120 -> 1060 ast.parse calls (1088 cache hits), wall time 19s -> 17s, output byte-identical apart from the budget lines. A mutated file is a different cache key, so a test that edits a SourceFile still sees fresh facts; the full architecture suite (288 tests) passes unchanged. Signed-off-by: song --- loopx/semantics/inventory.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/loopx/semantics/inventory.py b/loopx/semantics/inventory.py index 02a05716ea..b5f5a83f2d 100644 --- a/loopx/semantics/inventory.py +++ b/loopx/semantics/inventory.py @@ -13,6 +13,7 @@ import ast from collections import defaultdict from dataclasses import dataclass +from functools import lru_cache from pathlib import Path import re import subprocess @@ -115,7 +116,15 @@ def _assigned_name(node: ast.AST) -> tuple[str, ast.AST] | None: return None +@lru_cache(maxsize=4096) def python_facts(source: SourceFile) -> dict[str, list[dict[str, Any]]]: + """Inventory the Python facts of one source file. + + The cache is keyed by the frozen SourceFile (path, suffix, text), so a + mutated file is a different key and never serves stale facts; repeated + scans of the same tree (owner_values plus build_inventory in the drift + smoke) parse each file once instead of once per caller. + """ facts: dict[str, list[dict[str, Any]]] = { "enums": [], "closed_sets": [],