Skip to content

perf: optimize enforcement and role-link hot paths - #422

Open
H-Chris233 wants to merge 4 commits into
apache:masterfrom
H-Chris233:master
Open

perf: optimize enforcement and role-link hot paths#422
H-Chris233 wants to merge 4 commits into
apache:masterfrom
H-Chris233:master

Conversation

@H-Chris233

@H-Chris233 H-Chris233 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Changes

This PR includes performance optimizations for the enforcement and role-reachability hot paths:

  • Cache the p_eft token index to avoid repeated lookups for each policy.
  • Reuse precompiled regexes in key matching helpers.
  • Return early on CachedEnforcer cache hits to skip redundant argument conversion.
  • Add cached basic and ABAC benchmarks.
  • Add a zero-allocation fast path for has_link on chain-shaped role hierarchies (falls back to the legacy BFS on any branching graph, so behavior never changes).

Performance

  • Improves cached enforcement by approximately 36%–45% on cache hits.
  • has_link / RBAC role checks on chain-shaped hierarchies (the common RBAC shape) are roughly 55%–67% faster locally; the repo role_manager benchmarks show 20%–76% improvement (small/medium/large).

Verification

Tests, Clippy checks, and runtime combinations pass with both default and cached configurations. The has_link fast path is guarded by differential tests against the legacy BFS on thousands of random graphs.

Compute p_eft index once in src/enforcer.rs and reuse it instead of calling .position repeatedly, reducing repeated iteration. In src/model/function_map.rs add static compiled regexes (MAT_COLON, MAT_BRACE) and replace on-the-fly Regex::new calls with these statics, adjust replacements/escaping accordingly. Small performance-focused refactor with no behavior changes.
Add no-op benchmark functions gated by #[cfg(not(feature = "cached"))] so Criterion groups compile when the "cached" feature is disabled. Simplify event handler insertion by using or_default(), remove unnecessary references when calling private_enforce/private_enforce_with_context, and add early-return cache checks in enforce/enforce_with_context behind #[cfg(not(feature = "logging"))] to avoid work when a cached result exists. These changes clean up code, fix feature-flagged build issues, and optimize the cache fast path.
@github-actions

Copy link
Copy Markdown

Benchmark Results

group                                 changes                                master
-----                                 -------                                ------
b_benchmark_rbac_model_large          1.00      7.4±0.01ms        ? ?/sec    1.01      7.5±0.02ms        ? ?/sec
benchmark priority model              1.00   1059.5±5.10ns        ? ?/sec    1.01   1069.5±6.03ns        ? ?/sec
benchmark_abac_model                  1.01    831.6±4.20ns        ? ?/sec    1.00    827.1±3.97ns        ? ?/sec
benchmark_basic_model                 1.00    874.1±3.84ns        ? ?/sec    1.00    876.4±4.69ns        ? ?/sec
benchmark_key_match                   1.00      3.1±0.02µs        ? ?/sec    1.05      3.2±0.04µs        ? ?/sec
benchmark_raw                         1.00      0.0±0.00ns        ? ?/sec    1.00      0.0±0.00ns        ? ?/sec
benchmark_rbac_model                  1.00      2.3±0.02µs        ? ?/sec    1.02      2.3±0.01µs        ? ?/sec
benchmark_rbac_model_medium           1.00    608.2±2.63µs        ? ?/sec    1.04    631.8±6.84µs        ? ?/sec
benchmark_rbac_model_with_domains     1.00   1506.2±9.40ns        ? ?/sec    1.01   1522.3±5.72ns        ? ?/sec
benchmark_rbac_with_deny              1.00      3.9±0.03µs        ? ?/sec    1.06      4.1±0.04µs        ? ?/sec
benchmark_rbac_with_resource_roles    1.00   1074.3±5.49ns        ? ?/sec    1.00   1072.0±4.57ns        ? ?/sec
benchmark_role_manager_large          1.01      3.3±0.02ms        ? ?/sec    1.00      3.3±0.01ms        ? ?/sec
benchmark_role_manager_medium         1.01    220.3±2.01µs        ? ?/sec    1.00    217.9±1.04µs        ? ?/sec
benchmark_role_manager_small          1.00     23.1±0.61µs        ? ?/sec    1.08     24.9±0.23µs        ? ?/sec

@H-Chris233

Copy link
Copy Markdown
Contributor Author

Local benchmark verification

Environment: local Windows machine (noisy, ±20–30% run-to-run), 3 alternating
rounds per scenario with --measurement-time 10.

Optimization Scenario base 5b382a7 PR 86e8ccc Change
③ static regex key_get2 micro-bench (100× samples) 95.1 µs 39.7 µs −58%
③ static regex key_match4 micro-bench 89.3 µs 50.9 µs −43%
② p_eft index deny-effect model, 10k policies, full scan 7.62 ms 7.12 ms −6.5%
① early return cached basic/abac cache-hit (--features cached) 453 / 506 ns 455 / 497 ns no significant change (within noise)

Findings:

  • Regex static-fication (③) is real and reproducible: −43% to −58% on
    key_get2 / key_match4. Note it only affects key_get2 / key_get3 /
    key_match4 / key_match5keyMatch2 / keyMatch3 already used the
    pre-existing static MAT_B / MAT_P, so usual RBAC benchmarks don't
    exercise this change (consistent with CI showing no change there).
  • p_eft index (②) gives a small consistent gain (−6.5%) in a full-scan
    deny model with a large policy set; models that break early (the common
    some(where (p.eft == allow)) case) only call position 1–3 times, so the
    gain is negligible there.
  • The claimed 36–45% on cached cache-hits (①) did not reproduce locally:
    the early return removes try_into_vec + a duplicate cache.get, worth
    ~5–15% of the ~450 ns hit path, which is below measurement noise on this
    machine. A quieter environment or a dedicated micro-benchmark would be
    needed to confirm it.

Introduce matching_bfs::has_link and an adaptive DFS fast-path to speed up reachability checks when no role_matching_fn is used. The new has_link performs a zero-allocation, depth-limited DFS on chain-like hierarchies (with MAX_FAST_DEPTH and an edge budget) and falls back to the legacy BFS to preserve exact queue-drain depth semantics on branching or budget-exhausted graphs. Update RoleManager to call has_link when possible. Add Visit enum, visit helper, and comprehensive tests that verify equivalence with the legacy BFS and cover cycles, branching, deep graphs, and differential cases.
@H-Chris233 H-Chris233 changed the title perf: optimize enforcement hot paths perf: optimize enforcement and role-link hot paths Aug 17, 2026
@H-Chris233

Copy link
Copy Markdown
Contributor Author

Follow-up: I pushed an additional optimization on top of this branch — a zero-allocation fast path for has_link on chain-shaped role hierarchies, with automatic fallback to the legacy BFS on branching graphs (behavior is identical, guarded by differential tests).

Local benchmarks (alternating ABAB rounds):

  • benchmark_role_manager_small / medium / large: −20% / −26% / −76%
  • Chain-shaped reachability micro-benchmark: ~55–67% faster
  • Branching / deep hierarchies: no significant change

Ready for review.

@github-actions

Copy link
Copy Markdown

Benchmark Results

group                                 changes                                master
-----                                 -------                                ------
b_benchmark_rbac_model_large          1.00      4.8±0.34ms        ? ?/sec    1.25      6.0±0.14ms        ? ?/sec
benchmark priority model              1.00  1060.4±34.94ns        ? ?/sec    1.04  1099.4±40.92ns        ? ?/sec
benchmark_abac_model                  1.01   827.9±23.02ns        ? ?/sec    1.00   817.6±27.99ns        ? ?/sec
benchmark_basic_model                 1.00   895.9±25.67ns        ? ?/sec    1.02   913.3±48.24ns        ? ?/sec
benchmark_key_match                   1.04      2.8±0.17µs        ? ?/sec    1.00      2.7±0.12µs        ? ?/sec
benchmark_raw                         1.01      0.0±0.00ns        ? ?/sec    1.00      0.0±0.00ns        ? ?/sec
benchmark_rbac_model                  1.00      2.0±0.09µs        ? ?/sec    1.05      2.1±0.11µs        ? ?/sec
benchmark_rbac_model_medium           1.00    477.7±3.39µs        ? ?/sec    1.10    527.7±5.78µs        ? ?/sec
benchmark_rbac_model_with_domains     1.00  1434.7±63.44ns        ? ?/sec    1.04  1485.6±24.40ns        ? ?/sec
benchmark_rbac_with_deny              1.00      3.5±0.16µs        ? ?/sec    1.11      3.9±0.26µs        ? ?/sec
benchmark_rbac_with_resource_roles    1.03  1041.9±35.03ns        ? ?/sec    1.00  1014.6±40.80ns        ? ?/sec
benchmark_role_manager_large          1.00  1178.1±28.42µs        ? ?/sec    1.92      2.3±0.03ms        ? ?/sec
benchmark_role_manager_medium         1.00    120.8±0.82µs        ? ?/sec    1.48    179.4±1.82µs        ? ?/sec
benchmark_role_manager_small          1.00     12.0±0.43µs        ? ?/sec    1.56     18.7±0.20µs        ? ?/sec

@github-actions

Copy link
Copy Markdown

Benchmark Results

group                                 changes                                master
-----                                 -------                                ------
b_benchmark_rbac_model_large          1.00      6.7±0.01ms        ? ?/sec    1.40      9.4±0.06ms        ? ?/sec
benchmark priority model              1.00  1351.6±20.65ns        ? ?/sec    1.00  1354.6±18.18ns        ? ?/sec
benchmark_abac_model                  1.00   1071.3±6.14ns        ? ?/sec    1.01  1085.8±17.58ns        ? ?/sec
benchmark_basic_model                 1.00   1132.4±9.89ns        ? ?/sec    1.00  1137.4±15.16ns        ? ?/sec
benchmark_key_match                   1.00      4.0±0.03µs        ? ?/sec    1.02      4.1±0.06µs        ? ?/sec
benchmark_raw                         1.00      0.0±0.00ns        ? ?/sec    1.00      0.0±0.00ns        ? ?/sec
benchmark_rbac_model                  1.00      2.8±0.03µs        ? ?/sec    1.07      3.0±0.04µs        ? ?/sec
benchmark_rbac_model_medium           1.00    664.5±2.58µs        ? ?/sec    1.18    785.8±8.54µs        ? ?/sec
benchmark_rbac_model_with_domains     1.06      2.0±0.04µs        ? ?/sec    1.00  1907.4±58.53ns        ? ?/sec
benchmark_rbac_with_deny              1.00      4.7±0.03µs        ? ?/sec    1.07      5.0±0.04µs        ? ?/sec
benchmark_rbac_with_resource_roles    1.00  1380.2±19.20ns        ? ?/sec    1.01   1387.8±8.68ns        ? ?/sec
benchmark_role_manager_large          1.00   1853.4±8.50µs        ? ?/sec    2.26      4.2±0.01ms        ? ?/sec
benchmark_role_manager_medium         1.00    182.5±0.71µs        ? ?/sec    1.51    276.3±1.42µs        ? ?/sec
benchmark_role_manager_small          1.00     18.2±0.21µs        ? ?/sec    1.60     29.2±0.39µs        ? ?/sec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant