Performance Scan - 2026-08-23
Automated scan of src/apm_cli/ for algorithmic performance anti-patterns.
2 finding(s) identified.
Findings
[B] Linear scan inside nested loop -- security/executables.py:267-288 and 747-755
- Current: O(K * E * C * G) --
_map_grants iterates all entries of each grant_map
dict (O(G)) including calling _strip_version(stored_key) per entry. Called C=4-6
times per resolve_exec_decision, which is itself called inside a nested loop:
for key in candidate_keys: for exec_type in ALL_EXEC_TYPES (lines 747-755) and
a second nested loop at lines 779-782. K=candidate_keys, E=exec_types (~4),
G=grant_map size.
- Proposed: O(K * E) after O(G) pre-build -- build a
{stripped_key: {exec_type: bool}} index from each grant_map (user_allow,
user_deny, project_allow, project_deny) once before the outer loop.
- Fix: In
_map_grants, add an optional pre-built normalized-key index parameter;
before the outer for key in candidate_keys loop, call a helper that produces
dict[str, dict[str, bool]] keyed by _strip_version(stored_key) for each
grant_map, then replace the linear scan with an O(1) dict lookup.
[B] O(n*m) prefix scan on deployed files -- bundle/lockfile_enrichment.py:151
- Current: O(n * m) --
[f for f in deployed_files if any(f.startswith(p) for p in prefixes)] iterates all deployed files (n) and for each file checks all
target prefixes (m) until a match is found. Called during apm pack and bundle
export operations.
- Proposed: O(n * log(m)) or amortised O(n) -- sort
prefixes once and use
bisect to find the longest candidate prefix per file, or group files by
common prefix using a trie-like dict keyed on the first path segment.
- Fix: Sort
prefixes by length descending once before the comprehension and
break early; for large deployments, build a {first_segment: [prefixes]} dict
to skip non-matching prefixes in O(1) per file. Low-priority: m is typically
10-20 entries, so the fix matters mainly for deployments with hundreds of files.
Scan coverage
- src/apm_cli/ (445 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 · 141.7 AIC · ⌖ 8.78 AIC · ⊞ 7.3K · ◷
Performance Scan - 2026-08-23
Automated scan of src/apm_cli/ for algorithmic performance anti-patterns.
2 finding(s) identified.
Findings
[B] Linear scan inside nested loop -- security/executables.py:267-288 and 747-755
_map_grantsiterates all entries of each grant_mapdict (O(G)) including calling
_strip_version(stored_key)per entry. Called C=4-6times per
resolve_exec_decision, which is itself called inside a nested loop:for key in candidate_keys: for exec_type in ALL_EXEC_TYPES(lines 747-755) anda second nested loop at lines 779-782. K=candidate_keys, E=exec_types (~4),
G=grant_map size.
{stripped_key: {exec_type: bool}}index from each grant_map (user_allow,user_deny, project_allow, project_deny) once before the outer loop.
_map_grants, add an optional pre-built normalized-key index parameter;before the outer
for key in candidate_keysloop, call a helper that producesdict[str, dict[str, bool]]keyed by_strip_version(stored_key)for eachgrant_map, then replace the linear scan with an O(1) dict lookup.
[B] O(n*m) prefix scan on deployed files -- bundle/lockfile_enrichment.py:151
[f for f in deployed_files if any(f.startswith(p) for p in prefixes)]iterates all deployed files (n) and for each file checks alltarget prefixes (m) until a match is found. Called during
apm packand bundleexport operations.
prefixesonce and usebisectto find the longest candidate prefix per file, or group files bycommon prefix using a trie-like dict keyed on the first path segment.
prefixesby length descending once before the comprehension andbreak early; for large deployments, build a
{first_segment: [prefixes]}dictto skip non-matching prefixes in O(1) per file. Low-priority: m is typically
10-20 entries, so the fix matters mainly for deployments with hundreds of files.
Scan coverage
C (unconditional expensive ops), D (redundant config parsing),
E (heavy top-level imports), F (sequential independent I/O)