You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[PERF] Avoid repeated path matching when evaluating dependency rules #58
[PERF] Avoid repeated path matching when evaluating dependency rules
Problem
On a generated project with 5,005 Python files and 13,738 import edges, DependOnFileCondition.check() reuses the in-process graph as intended, but gather_depend_on_file_violations() still scans every projected edge and calls matches_pattern() repeatedly. The matcher normalizes or splits path labels
and runs a regex for each call. A suite of rules therefore spends most of its
time evaluating the same paths again.
In the comparison benchmark (ArchUnitPython 1.5.0 at commit 09c9d199dec095aaa60be227bfd7b5c2b7ffb107, Python 3.13.4, Windows 11),
the published fresh-process median for one direct boundary rule at 5,000 leaf
modules was 3.59 s. A separate exploratory phase profile showed:
Corpus
Ten additional rules, current matcher
Per-rule memoized matches
Violation count
1,000 leaf modules
1.86 s
0.76 s
1,494 in both runs
5,000 leaf modules
18.2 s
7.4 s
7,494 in both runs
These phase timings are one-off diagnostics, not publication-grade speedup
claims. The ten rules varied source and target folders and ran in one process.
The graph cache was hit for every additional rule; at 5,000 modules the cache
lookup totaled about 0.003 s, while rule evaluation took about 17.8 s.
Proposed investigation and implementation
Add a repeatable 1/10/50-rule benchmark linked to [Test] Benchmark for ArchUnitPython #55, including cold
command latency, warm suite latency, p50/p95, and peak memory.
Measure the number of unique source/target labels and repeated matcher
calls. Preserve arbitrary compiled-regex and glob behavior.
Match each unique label against each filter only once within a rule;
normalize or split path labels once. Consider indexing outgoing edges by
source if the simpler change does not remove enough work.
Keep any cache bounded to a check or explicit suite context. Avoid a global
cache that retains paths from unrelated projects or becomes stale after
edits.
Acceptance criteria
Existing behavior and violation evidence remain unchanged for glob and regex
filters, positive/negative rules, Windows paths, and empty-test protection.
Clean and seeded-violation benchmark corpora still produce the same results.
Report before/after median, p95, and memory for 1,000 and 5,000 modules,
including 1/10/50-rule suites. The rule-evaluation phase should improve
materially without a correctness or memory regression.
I tried the per-check label cache proposed here. gather_depend_on_file_violations()
now evaluates a source or target label against the rule's filters once per
distinct label, with the cache discarded after that check. This preserves
the rule and violation semantics and adds no persistent state.
Paired local fresh-process runs (one warm-up + five measured runs per size,
same Windows 11 / Python 3.13.4 / pytest 8.4.2 setup) gave:
Leaf modules
Before median
After median
Change
100
0.852 s
0.882 s
effectively unchanged
1,000
1.342 s
1.191 s
11.3% faster
5,000
4.013 s
2.826 s
29.6% faster
All clean, seeded forbidden-edge, and cycle gates passed. The full test suite
passed (294 tests). A single-run diagnostic for ten additional rules on the
5,000-module graph fell from 18.2 s to 7.62 s, with the same 7,494 violations;
that suite result still needs repeated measurement and memory data before it
becomes a public benchmark claim.
Update: this matcher change is now included in PR #60, ported to current main and passing CI on Python 3.10–3.13. The earlier numbers above were measured before the later extraction optimization; the PR has a fresh paired comparison against 1.7.0. Regression benchmark work remains in #55.
Activity
TristanKruse commentedon Sep 24, 2026
I tried the per-check label cache proposed here.
gather_depend_on_file_violations()now evaluates a source or target label against the rule's filters once per
distinct label, with the cache discarded after that check. This preserves
the rule and violation semantics and adds no persistent state.
Paired local fresh-process runs (one warm-up + five measured runs per size,
same Windows 11 / Python 3.13.4 / pytest 8.4.2 setup) gave:
All clean, seeded forbidden-edge, and cycle gates passed. The full test suite
passed (294 tests). A single-run diagnostic for ten additional rules on the
5,000-module graph fell from 18.2 s to 7.62 s, with the same 7,494 violations;
that suite result still needs repeated measurement and memory data before it
becomes a public benchmark claim.
Update: this matcher change is now included in PR #60, ported to current
mainand passing CI on Python 3.10–3.13. The earlier numbers above were measured before the later extraction optimization; the PR has a fresh paired comparison against 1.7.0. Regression benchmark work remains in #55.