Performance & Benchmarks
Where WorkflowForge sits on BenchmarkDotNet runs: internal numbers, comparisons, targets, and tuning notes.
Version: 2.2.0
Test System: Windows 11 (25H2), Intel 11th Gen i7-1185G7, .NET SDK 10.0.103
Runtimes: .NET 10.0.3, .NET 8.0.24, .NET Framework 4.8.1
BenchmarkDotNet: v0.15.8, 10 iterations per job
Last Updated: 14 September 2026 (2.2.0 hot-path release; full comparative BenchmarkDotNet re-run on release runner before tag)
Reading the 2.1.0 → 2.2.0 deltas. Several numbers below moved by 10–1000x since the last published run. Before treating that as a real speedup (or a regression, where a competitive multiplier shrank), know which of three things caused it:
- Harness fix, not code. The 2.1.0 run used a
RunStrategy/iteration-count combination meant for tens-of-ms+ work; on nanosecond/microsecond operations it measured JIT/GC/scheduler noise instead of the real cost. Fixed this release. Control case:DelayOperationExecution, which actually sleeps, moved from 15,192μs to 16.1ms — i.e. ~0%, because real work isn’t affected by the fix. Fast in-process operations moved 100–1000x (e.g. operation creation: ~1.7μs → ~50ns) — that’s the noise leaving, not new code making creation 30x faster.- A fairer comparison, not a regression. Scenario 7 (“Creation Overhead”)’s advantage vs. Elsa fell from 74–212x to a plain 2–3x. Elsa’s old number included rebuilding its DI container on every measured call (a bug in our benchmark harness, since fixed by moving that setup out of the timed region) — so the old multiplier was comparing our real cost against Elsa’s DI-bootstrap cost, not its creation cost. The new number is the fair one.
- Real code changes. Where a change touched the exact hot path being measured — e.g. the retried-operations-bypass-middleware fix,
LoggingMiddlewareno longer being inert — some of the remaining delta is genuine. Concurrency scaling (8.0x @ 8 workers, 15.9x @ 16) is unchanged from 2.1.0, which is the control for “did real throughput change”: it didn’t move, because that ratio cancels out most harness noise on its own.
Table of Contents
- Overview
- Internal Performance Benchmarks
- Competitive Performance Summary
- Performance Targets and Guidelines
- Optimization Guide
- Benchmark Methodology
- Version History
- Related Documentation
Overview
The engine optimizes for low-latency in-process orchestration:
- Nanoseconds to low microseconds on the hot path: CPU-bound ops median about 74ns–9.5μs in the harness (delays excluded).
- Small baseline: ~4.03KB, flat from 10 through 500 iterations in the minimal workflow test.
- Concurrency: about 8.0x with 8 workflows and 15.9x with 16 in the same suite.
- Property bag data flow: no per-step serialization or reflection on that path.
- No host-owned worker pool: parallelism is explicit (
ForEachWorkflowOperation, your schedulers).
On .NET 10, 8, and .NET Framework 4.8 the comparative harness reports 2-583x faster wall times and 1–533x lower allocations than Workflow Core and Elsa for the scripted scenarios we mirrored. Ratios are tied to those scripts and hardware, not every app.
Internal Performance Benchmarks
These internal benchmarks measure WorkflowForge in isolation across all three runtimes:
| Metric | .NET 8.0 | .NET 10.0 | .NET FX 4.8 |
|---|---|---|---|
| Operation Execution | 76ns–9.5μs median | 74ns–9.5μs median | 216ns–32μs median |
| Operation Creation | ~51ns median | ~48ns median | ~60ns median |
| Workflow Throughput | ~4–211μs (custom, 1–50 ops) | ~4–211μs (custom, 1–50 ops) | ~5–345μs (1–50 ops) |
| Memory Baseline | 4.03KB (constant) | 4.03KB (constant) | N/A‡ |
| Concurrent Scaling | 8.0x (8 wf), 15.9x (16 wf) | 8.0x (8 wf), 15.9x (16 wf) | 8.1x (8 wf), 16.2x (16 wf) |
‡ .NET Framework 4.8 does not report memory allocation metrics in BenchmarkDotNet.
Observations
- Custom operations allocate the least here (about 392 B per run in the harness).
- Logging operations post the shortest times (~74ns on .NET 10.0).
- The minimal-allocation workflow holds 4,126 B from 10 through 500 iterations.
- Ordinary paths in this matrix stayed off Gen2.
For operation-by-operation results, throughput scaling, memory patterns, and concurrency charts, see Internal Benchmarks.
Competitive Performance Summary
WorkflowForge is run beside Workflow Core and Elsa on twelve shared scenarios (same script) on .NET 10.0, 8.0, and .NET Framework 4.8. Peak ratio in our log: 584x wall time (state machine vs Elsa, .NET 10.0). Shortest median: 6.90μs (creation overhead, .NET FX 4.8). Aggregate bands from the tables: 2–583x time, 1–533x allocation.
Competitive Benchmark Summary (Median, 10 iterations)
Execution Time (.NET 8.0)
| # | Scenario | WorkflowForge | Workflow Core | Elsa | Speed Advantage |
|---|---|---|---|---|---|
| 1 | Sequential (10 ops) | 127μs | 5661μs | 18257μs | 45-144x |
| 2 | Data Passing (10 ops) | 133μs | 5571μs | 20319μs | 42-153x |
| 3 | Conditional (10 ops) | 129μs | 5559μs | 18548μs | 43-143x |
| 4 | Loop (50 items) | 337μs | 26325μs | 63196μs | 78-188x |
| 5 | Concurrent (8 workers) | 284μs | 39289μs | 110279μs | 138-388x |
| 6 | Error Handling | 107μs | 1027μs | 7383μs | 10-69x |
| 7 | Creation Overhead | 22.1μs | 44.4μs | 6.05μs | 2x |
| 8 | Complete Lifecycle | 105μs | N/A | 6580μs | 63x |
| 9 | State Machine (25) | 59.7μs | 13798μs | 33184μs | 231-556x |
| 10 | Long Running* | 72ms | 71ms | 85ms | 1x |
| 11 | Parallel (16 ops) | 51.7μs | 1847μs | 27308μs | 36-528x |
| 12 | Event-Driven* | 8ms | 9ms | 21ms | 1-3x |
*Long Running and Event-Driven are delay-bound; advantage is in memory.
Execution Time (.NET 10.0)
| # | Scenario | WorkflowForge | Workflow Core | Elsa | Speed Advantage |
|---|---|---|---|---|---|
| 1 | Sequential (10 ops) | 119μs | 7074μs | 22028μs | 60-186x |
| 2 | Data Passing (10 ops) | 129μs | 7070μs | 22211μs | 55-172x |
| 3 | Conditional (10 ops) | 138μs | 7117μs | 21034μs | 52-153x |
| 4 | Loop (50 items) | 322μs | 32922μs | 58601μs | 102-182x |
| 5 | Concurrent (8 workers) | 260μs | 47648μs | 99384μs | 183-383x |
| 6 | Error Handling | 69.4μs | 1225μs | 9249μs | 18-133x |
| 7 | Creation Overhead | 17.9μs | 45.6μs | 4.25μs | 3x |
| 8 | Complete Lifecycle | 108μs | N/A | 7464μs | 69x |
| 9 | State Machine (25) | 59.3μs | 17133μs | 34627μs | 289-584x |
| 10 | Long Running* | 72ms | 72ms | 86ms | 1x |
| 11 | Parallel (16 ops) | 50.2μs | 2214μs | 22285μs | 44-444x |
| 12 | Event-Driven* | 8ms | 8ms | 21ms | 1-3x |
Execution Time (.NET Framework 4.8)
| # | Scenario | WorkflowForge | Workflow Core | Speed Advantage |
|---|---|---|---|---|
| 1 | Sequential (10 ops) | 110μs | 5896μs | 54x |
| 2 | Data Passing (10 ops) | 122μs | 5904μs | 48x |
| 3 | Conditional (10 ops) | 117μs | 5892μs | 50x |
| 4 | Loop (50 items) | 326μs | 28308μs | 87x |
| 5 | Concurrent (8 workers) | 141μs | 37556μs | 267x |
| 6 | Error Handling | 81.0μs | 10284μs | 127x |
| 7 | Creation Overhead | 6.90μs | 13.9μs | 2x |
| 8 | Complete Lifecycle | 33.6μs | N/A | N/A |
| 9 | State Machine (25) | 55.9μs | 15166μs | 272x |
| 11 | Parallel (16 ops) | 30.6μs | 1561μs | 51x |
Elsa does not support .NET Framework 4.8 and is excluded from this comparison.
Competitive Memory Summary (.NET 8.0)
| # | Scenario | WorkflowForge | Workflow Core | Elsa | Memory Advantage |
|---|---|---|---|---|---|
| 1 | Sequential (10 ops) | 18.20KB | 415.16KB | 3.05MB | 23-172x |
| 2 | Data Passing (10 ops) | 16.84KB | 415.30KB | 3.03MB | 25-184x |
| 3 | Conditional (10 ops) | 20KB | 416.54KB | 3.03MB | 21-156x |
| 4 | Loop (50 items) | 96.18KB | 2.01MB | 10.76MB | 21-115x |
| 5 | Concurrent (8 workers) | 158.55KB | 3.10MB | 19.18MB | 20-124x |
| 6 | Error Handling | 9.24KB | 44.91KB | 1.08MB | 5-119x |
| 7 | Creation Overhead | 4.49KB | 8.58KB | 424 B | 0.1-2x |
| 8 | Complete Lifecycle | 4.46KB | N/A | 1.02MB | 235x |
| 9 | State Machine (25) | 23.94KB | 1.05MB | 5.89MB | 45-252x |
| 10 | Long Running* | 5.77KB | 260.61KB | 2.21MB | 45-393x |
| 11 | Parallel (16 ops) | 9.00KB | 122.84KB | 4.67MB | 14-531x |
| 12 | Event-Driven* | 4.21KB | 36.21KB | 1.03MB | 9-249x |
Competitive Memory Summary (.NET 10.0)
| # | Scenario | WorkflowForge | Workflow Core | Elsa | Memory Advantage |
|---|---|---|---|---|---|
| 1 | Sequential (10 ops) | 18.22KB | 408.23KB | 3.01MB | 22-169x |
| 2 | Data Passing (10 ops) | 16.86KB | 408KB | 3.02MB | 24-183x |
| 3 | Conditional (10 ops) | 20KB | 407.38KB | 3.01MB | 20-154x |
| 4 | Loop (50 items) | 96.20KB | 1.96MB | 10.89MB | 21-116x |
| 5 | Concurrent (8 workers) | 158.66KB | 3.04MB | 19.07MB | 20-123x |
| 6 | Error Handling | 7.90KB | 43KB | 1.12MB | 5-145x |
| 7 | Creation Overhead | 4.51KB | 8.59KB | 424 B | 0.1-2x |
| 8 | Complete Lifecycle | 4.48KB | N/A | 1.00MB | 229x |
| 9 | State Machine (25) | 24KB | 1.03MB | 5.85MB | 44-250x |
| 10 | Long Running* | 5.78KB | 255.54KB | 2.19MB | 44-389x |
| 11 | Parallel (16 ops) | 8.75KB | 120.43KB | 4.56MB | 14-533x |
| 12 | Event-Driven* | 4.23KB | 35.73KB | 1.06MB | 8-258x |
Summary bands (all measured scenarios; delay-heavy 10/12 excluded from speed band math, September 2026 run): 2–583x faster execution; 1–533x lower allocation vs Workflow Core/Elsa.
- Results captured on Windows 11 (25H2), Intel i7-1185G7, BenchmarkDotNet v0.15.8, 10 iterations per job.
- Median values used; Elsa omitted on .NET Framework 4.8.
For scenario breakdowns, parameter sweeps, and architectural comparisons, see Competitive Analysis.
Performance Targets and Guidelines
WorkflowForge maintains the following performance targets:
| Metric | Target | Status |
|---|---|---|
| Operation execution | <100μs median for simple operations | Met (sub-μs for logging; chained ops ~10μs) |
| Workflow creation | <5μs overhead | Met (~48ns op creation; ~18μs workflow creation in comparative harness) |
| Memory baseline | <5KB for minimal workflows | Met (4.03KB) |
| GC pressure | No Gen2 collections in typical workloads | Met |
| Concurrent scaling | Near-linear | Met (8.0x for 8, 15.9x for 16) |
Targets below come straight from the same BenchmarkDotNet matrix. Keep workflows on custom ops, modest property payloads, and tight middleware stacks if you need the same envelope.
Optimization Guide
1. Choose the Right Operation Type
- Custom class-based operations use the least memory in benchmarks (~392 B); they are the default choice when you care about allocation.
- Logging operations measure fastest (~74ns on .NET 10.0) for tiny steps.
- Delegate operations are handy but cost a bit more than custom ops (~1.5μs vs ~1.3μs in the same harness).
2. Reuse Workflow Definitions
Build workflows once and execute many times. Operation creation is tens of nanoseconds; reuse still helps when definitions are heavy.
3. Optimize Data Passing
- Use
foundry.Propertiesfor all data flow - Cache property reads in loops instead of repeated lookups
- Avoid large object allocations in hot paths
4. Use Concurrency
Use ForEachWorkflowOperation for parallel execution of independent operations. Concurrency scales near-linearly (8.0x for 8 workflows, 15.9x for 16).
5. Minimize Middleware
Add only necessary middleware. Each middleware adds ~1–5μs overhead per operation.
6. Avoid Large Object Allocations
Large allocations (>85KB) trigger Gen2 GC and degrade throughput. Keep operation payloads small where possible.
7. Use .NET 10.0 Where Available
.NET 10.0 shows improved exception handling (16.6μs vs 33.9μs) and some operation gains.
For detailed code examples and patterns, see Internal Benchmarks and Operations.
Benchmark Methodology
Configuration
- Framework: BenchmarkDotNet v0.15.8
- Runtimes: .NET 10.0.3, .NET 8.0.24, .NET Framework 4.8.1
- Iterations: 10 per benchmark job
- Warmup: per BenchmarkDotNet default job
- Invocation: 1 per iteration
Hardware
- OS: Windows 11 (25H2)
- CPU: Intel 11th Gen i7-1185G7
- SDK: .NET SDK 10.0.103
Statistical Approach
- Median values used for comparison (more stable than mean)
- Standard deviation < 20% of mean for most scenarios
- P95 values available for consistency verification
- All scenarios implement identical logic across frameworks
Reproduction
Internal benchmarks:
cd src/benchmarks/WorkflowForge.Benchmarks
dotnet run -c Release -f net48 -f net8.0 -f net10.0
Competitive benchmarks:
cd src/benchmarks/WorkflowForge.Benchmarks.Comparative
dotnet run -c Release -f net48 -f net8.0 -f net10.0
BenchmarkDotNet writes output under BenchmarkDotNet.Artifacts/results/. Expect full comparative runs to take about 30–60 minutes.
Version History
Version 2.2.0 (September 2026)
- Refreshed competitive and internal benchmark documentation from September 2026 BenchmarkDotNet runs (10 iterations per job).
Version 2.1.2 (July 2026)
- Fixed ILRepacked extensions (Polly/OpenTelemetry/Serilog) missing transitive dependencies
- Implemented foundry performance monitoring (
EnablePerformanceMonitoring/GetPerformanceStatistics) - Fixed pooled-foundry state leak and audit workflow-name resolution
- Centralized package versioning and shared metadata
Version 2.1.1 (March 2026)
- Multi-target .NET 10.0, .NET 8.0, .NET Framework 4.8
- Sealed operation classes
- ConfigureAwait optimization
- Compensation always attempts RestoreAsync (no-op default)
Version 2.0.0 (January 2026)
- Initial release with 12 competitive scenarios
- Head-to-head comparison with Workflow Core and Elsa Workflows
Related Documentation
- Internal Benchmarks: operation performance, throughput, memory, concurrency
- Competitive Analysis: scenario breakdowns, parameter sweeps, architectural differences
- Architecture Overview: design principles and execution model
- Operations: operation types and middleware pipeline