Skip to content

Add opt-in parallel rule compilation (improved on top of #741)#744

Merged
YogeshPraj merged 2 commits into
microsoft:mainfrom
YogeshPraj:feature/parallel-rule-compilation-improved
Jun 11, 2026
Merged

Add opt-in parallel rule compilation (improved on top of #741)#744
YogeshPraj merged 2 commits into
microsoft:mainfrom
YogeshPraj:feature/parallel-rule-compilation-improved

Conversation

@YogeshPraj

Copy link
Copy Markdown
Contributor

Summary

Picks up @benluersen's work from #741 and adds the three improvements I flagged in that PR's review, packaged for the upcoming release.

Ben's original commit (d8087f1) is preserved in the history; my changes layer on top as a separate commit so attribution stays clean. If @benluersen would rather take these changes back into #741 directly and have this one close, I'm happy with that outcome too — the goal is just to unblock the release. Otherwise this PR can land as-is.

What's preserved from #741

  • New ReSettings.EnableParallelRuleCompilation (default false).
  • Parallel.For rule compilation when the flag is enabled.
  • Original rule ordering in the compiled dictionary.
  • AggregateException unwrap so callers see the underlying compile error.

Original perf claim from #741: 20,000-rule workflow, 16.2s serial → 4.7s parallel (~3.4×).

What this PR adds on top

1. Guard against UseFastExpressionCompiler (the silent footgun)

The #741 description noted a 2.7× regression (12.9s vs 4.7s) when both EnableParallelRuleCompilation and UseFastExpressionCompiler are set. Users would enable the flag for a warmup win and silently get slower. The engine now declines to parallelize that combination and falls back to serial:

var shouldParallelize = _reSettings.EnableParallelRuleCompilation
                     && !_reSettings.UseFastExpressionCompiler
                     && enabledRules.Length >= MinRulesForParallelCompilation;

benluersen and others added 2 commits June 11, 2026 23:11
Rule compilation during workflow registration was strictly serial. For
workflows with very large rule counts (10k+), warmup is dominated by
this loop even after expression parsing is fixed.

Adds ReSettings.EnableParallelRuleCompilation (default false). When
enabled, rules are compiled with Parallel.For and results are added to
the compiled-rule dictionary in the original order. An
AggregateException from the parallel loop is unwrapped so the first
failing rule surfaces its original exception, preserving the serial
error contract (verified by the existing
ExecuteRule_MissingMethodInExpression_ReturnsRulesFailed test).

Benchmark, 20,000 unique rules with local params: 16.2s serial -> 4.7s
parallel on a 16-thread machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builds on microsoft#741 by @benluersen. The original opt-in parallel rule
compilation is sound but had two latent footguns and no test coverage
for the parallel path:

1. UseFastExpressionCompiler interaction (~2.7× regression when both
   flags are on, per the PR description) — users would flip the flag and
   silently get slower. The engine now declines to parallelize when
   UseFastExpressionCompiler = true and falls back to serial.

2. Below ~32 rules, Parallel.For's scheduling overhead exceeds the
   speedup. Added a MinRulesForParallelCompilation threshold so small
   workflows aren't penalised by enabling the flag globally.

3. catch (AggregateException ae) accessed ae.InnerExceptions[0]
   without bounds-checking. Replaced with a `when` filter so the catch
   only matches when there's actually an inner exception to rethrow.

XML doc on ReSettings.EnableParallelRuleCompilation now spells out both
fallback conditions so the contract is obvious without reading the
implementation.

New ParallelRuleCompilationTest covers:
- Parallel and serial produce identical RuleResultTree shape and outcomes
- The first compile failure surfaces as a per-rule ExceptionMessage, not
  an AggregateException
- UseFastExpressionCompiler + parallel still produces correct results
  (the fallback is silent, only observable in benchmarks)
- Sub-threshold workflows execute correctly with the flag enabled

All 174 unit tests pass on net6 / net8 / net9 / net10.

Co-authored-by: Ben Luersen <ben.luersen@gmail.com>

@benluersen benluersen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me

@YogeshPraj YogeshPraj merged commit aeb2932 into microsoft:main Jun 11, 2026
3 checks passed
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.

3 participants