perf(compile): fix quadratic and redundant-resolve costs in placement - #2878
perf(compile): fix quadratic and redundant-resolve costs in placement#2878Sergei Kozyrev (mukkumayc) wants to merge 3 commits into
Conversation
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
🟡 Changes recommended
The newly introduced memoization caches can grow unbounded across repeated compiles because they are not cleared when the long-lived ContextOptimizer instance is reused.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Improves apm compile placement performance by removing redundant path normalization and repeated scans inside ContextOptimizer, targeting large monorepos with multiple overlapping scoped applyTo patterns.
Changes:
- Memoizes per-file relative-path strings and uses cached
Set[str]membership for**glob matching. - Fixes an O(n^2) mean/variance recomputation in distribution scoring and removes redundant directory-cache rescans.
- Short-circuits hierarchical coverage checks to avoid full rescans per candidate placement.
File summaries
| File | Description |
|---|---|
src/apm_cli/compilation/context_optimizer.py |
Adds memoized relpath + resolved-dir caches and refactors matching/coverage calculations to reduce redundant work and quadratic behavior in placement optimization. |
Review details
Suppressed comments (1)
src/apm_cli/compilation/context_optimizer.py:168
_resolved_dir_cacheis introduced as an unbounded memoization dict but is not cleared between compile runs. SinceContextOptimizeris reused across compiles, this cache can grow without bound and retain resolved Paths long-term; it should be cleared when starting a new placement optimization (similar to_glob_cache).
self._inheritance_cache: builtins.dict[Path, builtins.list[Path]] = {} # (#171)
self._resolved_dir_cache: builtins.dict[Path, Path] = {}
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
edcc6eb to
1acbe91
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The changes appear to be performance-only refactors with cache invalidation handled per run, and the only identified issue is a small maintainability cleanup (dead try/except) with a straightforward fix.
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
ContextOptimizer had several costs that only show up once a project has multiple scoped applyTo patterns each matching large, overlapping directory sets: - _file_matches_pattern and _safe_recursive_glob resolved every file path independently per pattern instead of reusing a memoized relative path. - _calculate_distribution_score recomputed the depth mean inside its variance loop (O(n^2)) and rescanned _directory_cache for a count that's just its length. - _is_hierarchically_covered, _find_minimal_coverage_placement, and _is_instruction_relevant resolved paths that were already canonical _directory_cache keys. - _optimize_single_point_placement rescanned every matching directory per candidate instead of short-circuiting on the first miss. No behavioral changes; all existing tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Match the existing pattern for _glob_cache/_glob_set_cache: reset per-instance path caches at the start of optimize_instruction_placement so repeated calls on the same ContextOptimizer stay consistent with the rest of the scan-dependent cache resets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- _file_matches_pattern's non-** branch no longer wraps _portable_rel_path in a try/except ValueError -- the helper already swallows it internally and falls back to portable_relpath (which never raises). - _single_placement_covers_all now delegates to _is_hierarchically_covered instead of re-deriving the same relative_to/ValueError check, keeping one source of truth for the mandatory-coverage constraint. - Fix test_single_dir_returns_that_dir (both duplicated copies) to pass an already-resolved directory, matching its sibling tests and the already-canonical-paths invariant the placement code now relies on -- it previously only passed because pytest's tmp_path happens to already be canonical on Linux. Re-benchmarked against apm-placement-benchmark's proj_1000/2000/10000 fixtures after these changes: speedups unchanged from the perf branch's prior numbers (up to 43.7x on proj_10000), confirming no regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71f6840 to
fb19e23
Compare
Motivation
I hit a real performance problem running
apm compileon a large internal monorepo, roughly 159,000 directories and 294,000 files, with several scopedapplyTopatterns. Compile time was bad enough that I dug intoContextOptimizerand found the causes below.I mostly write JS, not Python, so I used Claude Code to implement this fix. I reviewed the diff and verified it with a before after benchmark, but flagging this for transparency.
Summary
ContextOptimizerhad several costs that only show up once a project has multiple scoped (non-universal)applyTopatterns, each matching a large, overlapping set of directories, the shape of a real monorepo with per-module conventions. Four issues stood out:_file_matches_patternand_safe_recursive_globresolved every file path independently per pattern instead of reusing a memoized relative path._calculate_distribution_scorerecomputed the depth mean inside its variance loop (O(n²)) and rescanned_directory_cachefor a count that's just its length._is_hierarchically_covered,_find_minimal_coverage_placement, and_is_instruction_relevantresolved paths that were already canonical_directory_cachekeys._optimize_single_point_placementrescanned every matching directory per candidate instead of short-circuiting on the first miss.No behavioral changes. Only removing provably redundant work.
Benchmark
Before/after comparison on synthetic monorepo-shaped projects: https://github.com/mukkumayc/apm-placement-benchmark
Test plan