Performance Scan - 2026-08-13
Automated scan of src/apm_cli/ for algorithmic performance anti-patterns.
3 finding(s) identified.
Findings
[A] Quadratic nested loop -- uninstall/engine.py:74-94 and 178-250
- Current: O(n*m) --
_surviving_local_refs_at_install_path iterates all surviving_dependencies (m) for every package in packages_to_remove (n); called twice: lines 178-250 and 734-753.
- Proposed: O(n+m) with a pre-built index.
- Fix: Before the outer loop, build
install_path_to_survivors: dict[Path, list] = {} by iterating surviving_dependencies once (grouping by get_install_path), then replace the inner linear scan with a single dict lookup per package.
[D] Triple dep_names recomputation -- policy/policy_checks.py:155,198,251
- Current: O(3n) --
_check_required_packages (line 155), _check_required_packages_deployed (line 198), and _check_required_executable_untrusted (line 251) each independently rebuild dep_names = {dep.get_canonical_dependency_string().split("#")[0] for dep in deps} from the same deps list. All three are called in sequence from the main runner at lines 1239-1245.
- Proposed: O(n) -- compute once and reuse.
- Fix: Compute
dep_names once in the main runner (or a shared helper) and pass it as a parameter to each check function that needs it.
[B] Linear scan in _map_grants per (package, exec_type) pair -- security/executables.py:256-262 (low priority)
- Current: O(m) per call --
_map_grants iterates all entries in grant_map.items() performing _strip_version comparisons for each; called up to 4 times per (package, exec_type) pair from _resolve_trust (lines 327-343), and materialize_exec_map calls _resolve_trust for every candidate key crossed with every exec type.
- Proposed: O(1) per lookup after O(m) pre-build -- index grant maps as
{_strip_version(k): (k, entry)} once before the resolution loop.
- Fix: Pre-build a version-blind index for each grant map before calling
materialize_exec_map or the tight exec_status_for_declaration loop (lines 742-760), then replace the linear scan with a dict lookup.
Scan coverage
- src/apm_cli/ (430 files scanned)
- Patterns checked: A (quadratic loops), B (linear scan in loop),
C (unconditional expensive ops), D (redundant config parsing),
E (heavy top-level imports), F (sequential independent I/O)
Generated by Daily Performance Scanner · 133.2 AIC · ⌖ 6.24 AIC · ⊞ 7.3K · ◷
Performance Scan - 2026-08-13
Automated scan of src/apm_cli/ for algorithmic performance anti-patterns.
3 finding(s) identified.
Findings
[A] Quadratic nested loop -- uninstall/engine.py:74-94 and 178-250
_surviving_local_refs_at_install_pathiterates allsurviving_dependencies(m) for every package inpackages_to_remove(n); called twice: lines 178-250 and 734-753.install_path_to_survivors: dict[Path, list] = {}by iteratingsurviving_dependenciesonce (grouping byget_install_path), then replace the inner linear scan with a single dict lookup per package.[D] Triple dep_names recomputation -- policy/policy_checks.py:155,198,251
_check_required_packages(line 155),_check_required_packages_deployed(line 198), and_check_required_executable_untrusted(line 251) each independently rebuilddep_names = {dep.get_canonical_dependency_string().split("#")[0] for dep in deps}from the samedepslist. All three are called in sequence from the main runner at lines 1239-1245.dep_namesonce in the main runner (or a shared helper) and pass it as a parameter to each check function that needs it.[B] Linear scan in _map_grants per (package, exec_type) pair -- security/executables.py:256-262 (low priority)
_map_grantsiterates all entries ingrant_map.items()performing_strip_versioncomparisons for each; called up to 4 times per (package, exec_type) pair from_resolve_trust(lines 327-343), andmaterialize_exec_mapcalls_resolve_trustfor every candidate key crossed with every exec type.{_strip_version(k): (k, entry)}once before the resolution loop.materialize_exec_mapor the tightexec_status_for_declarationloop (lines 742-760), then replace the linear scan with a dict lookup.Scan coverage
C (unconditional expensive ops), D (redundant config parsing),
E (heavy top-level imports), F (sequential independent I/O)