@@ -4,62 +4,96 @@ This benchmark compares the performance overhead of Foundatio's `IResiliencePoli
44
55## Results Summary
66
7- | Method | Category | Mean | Ratio | Allocated |
8- | --------| ----------| ------| -------| -----------|
9- | Direct_NoRetry | 1_NoRetry | 2.84 ns | 1.00 | - |
10- | Foundatio_NoRetry | 1_NoRetry | 33.59 ns | 11.84x | 64 B |
11- | Polly_NoRetry | 1_NoRetry | 32.57 ns | 11.48x | 64 B |
12- | | | | | |
13- | Direct_WithRetry | 2_WithRetry | 2.84 ns | 1.00 | - |
14- | Foundatio_WithRetry | 2_WithRetry | 33.62 ns | 11.84x | 64 B |
15- | Polly_WithRetry | 2_WithRetry | 137.44 ns | 48.41x | 64 B |
16- | | | | | |
17- | Direct_NoRetry_WithResult | 3_NoRetry_WithResult | 3.09 ns | 1.00 | - |
18- | Foundatio_NoRetry_WithResult | 3_NoRetry_WithResult | 35.46 ns | 11.47x | 64 B |
19- | Polly_NoRetry_WithResult | 3_NoRetry_WithResult | 32.79 ns | 10.61x | 64 B |
20- | | | | | |
21- | Direct_WithRetry_WithResult | 4_WithRetry_WithResult | 3.10 ns | 1.00 | - |
22- | Foundatio_WithRetry_WithResult | 4_WithRetry_WithResult | 36.34 ns | 11.72x | 64 B |
23- | Polly_WithRetry_WithResult | 4_WithRetry_WithResult | 104.98 ns | 33.87x | 64 B |
7+ ### Synchronous Execution
8+
9+ | Method | Category | Mean | Allocated |
10+ | -------------------------- | ------------------ | --------- | --------- |
11+ | Direct_Sync_NoRetry | 1_Sync_NoRetry | 0.28 ns | - |
12+ | Foundatio_Sync_NoRetry | 1_Sync_NoRetry | 23.29 ns | - |
13+ | Polly_Sync_NoRetry | 1_Sync_NoRetry | 19.92 ns | - |
14+ | | | | |
15+ | Direct_Sync_WithRetry | 2_Sync_WithRetry | 0.18 ns | - |
16+ | Foundatio_Sync_WithRetry | 2_Sync_WithRetry | 23.01 ns | - |
17+ | Polly_Sync_WithRetry | 2_Sync_WithRetry | 121.87 ns | - |
18+ | | | | |
19+ | Direct_Sync_WithResult | 3_Sync_WithResult | 0.26 ns | - |
20+ | Foundatio_Sync_WithResult | 3_Sync_WithResult | 25.11 ns | - |
21+ | Polly_Sync_WithResult | 3_Sync_WithResult | 126.83 ns | 24 B |
22+
23+ ### Asynchronous Execution
24+
25+ | Method | Category | Mean | Allocated |
26+ | -------------------------- | ------------------ | --------- | --------- |
27+ | Direct_Async_NoRetry | 4_Async_NoRetry | 2.80 ns | - |
28+ | Foundatio_Async_NoRetry | 4_Async_NoRetry | 36.39 ns | 64 B |
29+ | Polly_Async_NoRetry | 4_Async_NoRetry | 36.61 ns | 64 B |
30+ | | | | |
31+ | Direct_Async_WithRetry | 5_Async_WithRetry | 2.83 ns | - |
32+ | Foundatio_Async_WithRetry | 5_Async_WithRetry | 36.86 ns | 64 B |
33+ | Polly_Async_WithRetry | 5_Async_WithRetry | 141.24 ns | 64 B |
34+ | | | | |
35+ | Direct_Async_WithResult | 6_Async_WithResult | 3.11 ns | - |
36+ | Foundatio_Async_WithResult | 6_Async_WithResult | 38.65 ns | 64 B |
37+ | Polly_Async_WithResult | 6_Async_WithResult | 115.35 ns | 64 B |
38+
39+ ### Zero-Allocation Patterns
40+
41+ | Method | Category | Mean | Allocated |
42+ | -------------------------- | ------------------ | --------- | --------- |
43+ | Direct_ZeroAlloc_Static | 7_ZeroAlloc_Static | 1.16 ns | - |
44+ | Foundatio_ZeroAlloc_Static | 7_ZeroAlloc_Static | 29.21 ns | - |
45+ | Polly_ZeroAlloc_Static | 7_ZeroAlloc_Static | 133.70 ns | - |
46+ | | | | |
47+ | Direct_ZeroAlloc_State | 8_ZeroAlloc_State | 1.04 ns | - |
48+ | Foundatio_ZeroAlloc_State | 8_ZeroAlloc_State | 30.71 ns | - |
49+ | Polly_ZeroAlloc_State | 8_ZeroAlloc_State | 130.71 ns | 88 B |
2450
2551## Key Findings
2652
27- ### No Retry Configured (Base Framework Overhead)
53+ ### Sync Performance
2854
29- When no retry policy is configured, both libraries perform nearly identically:
55+ | Scenario | Foundatio | Polly | Foundatio Advantage |
56+ | --------------- | --------- | --------- | --------------------- |
57+ | No retry | 23.3 ns | 19.9 ns | Polly ~ 17% faster |
58+ | With retry | 23.0 ns | 121.9 ns | ** 5.3x faster** |
59+ | With result | 25.1 ns | 126.8 ns | ** 5.1x faster** |
3060
31- | Scenario | Foundatio | Polly | Difference |
32- | ----------| -----------| -------| ------------|
33- | Async (void) | 33.6 ns | 32.6 ns | Polly ~ 3% faster |
34- | With Result | 35.5 ns | 32.8 ns | Polly ~ 8% faster |
61+ Sync execution in Foundatio allocates ** 0 bytes** across all scenarios. Polly allocates 24 bytes when returning results.
3562
36- ** Conclusion ** : For pass-through scenarios with no retry logic, Polly's empty pipeline has a slight edge.
63+ ### Async Performance
3764
38- ### With Retry Configured (Real-World Scenario)
65+ | Scenario | Foundatio | Polly | Foundatio Advantage |
66+ | --------------- | --------- | --------- | --------------------- |
67+ | No retry | 36.4 ns | 36.6 ns | ~ equal |
68+ | With retry | 36.9 ns | 141.2 ns | ** 3.8x faster** |
69+ | With result | 38.7 ns | 115.3 ns | ** 3.0x faster** |
3970
40- When retry policies are configured (3 attempts, even though no retries occur), Foundatio is significantly faster:
71+ Both libraries allocate 64 bytes for async execution (async state machine overhead).
4172
42- | Scenario | Foundatio | Polly | Foundatio Advantage |
43- | ----------| -----------| -------| ---------------------|
44- | Async (void) | 33.6 ns | 137.4 ns | ** 4.1x faster** |
45- | With Result | 36.3 ns | 105.0 ns | ** 2.9x faster** |
73+ ### Zero-Allocation Performance
4674
47- ** Conclusion ** : Foundatio maintains consistent ~ 34-36ns overhead regardless of configuration, while Polly 's overhead increases 3-4x when retry strategies are added.
75+ For performance-critical paths, Foundatio 's state-based overloads achieve ** zero heap allocations ** :
4876
49- ### Memory Allocations
77+ | Pattern | Foundatio | Polly | Foundatio Advantage |
78+ | -------------------- | ---------------- | --------------- | --------------------------------- |
79+ | Static lambda | 29.2 ns, ** 0 B** | 133.7 ns, 0 B | ** 4.6x faster** |
80+ | State-based | 30.7 ns, ** 0 B** | 130.7 ns, 88 B | ** 4.3x faster, zero allocations** |
5081
51- Both libraries allocate ** 64 bytes** per operation when using resilience policies. The direct call allocates nothing .
82+ Polly still allocates 88 bytes even with the state-based pattern, while Foundatio achieves true zero-allocation execution .
5283
53- ### Why Foundatio is Faster with Retry Configuration
84+ ## Why Foundatio is Faster
5485
5586** Foundatio's architecture:**
87+
5688- Simple ` do-while ` loop with direct property checks
5789- Null-conditional operators for optional features (` CircuitBreaker?.BeforeCall() ` )
5890- Direct field access with no indirection layers
5991- When ` Timeout <= 0 ` , no ` CancellationTokenSource ` allocations
60- - On success path: calls action, checks circuit breaker (null), returns
92+ - Dedicated sync ` Execute ` methods (no async overhead)
93+ - State-based overloads avoid closure allocations entirely
6194
6295** Polly's architecture:**
96+
6397- Pipeline-based with strategy composition
6498- Each strategy (retry, circuit breaker, timeout) is a separate component in a chain
6599- ` ResiliencePipeline.ExecuteAsync ` iterates through the strategy stack
@@ -73,7 +107,7 @@ The flexibility of Polly's composable pipeline architecture comes at a performan
73107### Foundatio Setup
74108
75109``` csharp
76- // No Retry: 1 attempt, no delay (measures base framework overhead)
110+ // No Retry: 1 attempt, no delay
77111_foundatioNoRetry = new ResiliencePolicyBuilder ()
78112 .WithMaxAttempts (1 )
79113 .WithDelay (TimeSpan .Zero )
@@ -89,9 +123,8 @@ _foundatioWithRetry = new ResiliencePolicyBuilder()
89123### Polly Setup
90124
91125``` csharp
92- // No Retry: Empty pipeline (no retry strategy)
93- _pollyNoRetry = new ResiliencePipelineBuilder ()
94- .Build ();
126+ // No Retry: Empty pipeline
127+ _pollyNoRetry = new ResiliencePipelineBuilder ().Build ();
95128
96129// With Retry: 2 retries (3 total attempts)
97130_pollyWithRetry = new ResiliencePipelineBuilder ()
@@ -103,29 +136,30 @@ _pollyWithRetry = new ResiliencePipelineBuilder()
103136 .Build ();
104137```
105138
106- ### Simulated Operations
139+ ### Zero-Allocation Examples
107140
108141``` csharp
109- // Minimal work to measure framework overhead, not operation cost
110- private ValueTask SimulateWorkAsync ()
142+ // Foundatio state-based (0 B allocations)
143+ _foundatioWithRetry . ExecuteAsync ( this , static ( state , ct ) =>
111144{
112- Interlocked .Increment (ref _counter );
113- return ValueTask .CompletedTask ;
114- }
145+ return state .SimulateWorkAsync ();
146+ });
115147
116- private ValueTask < int > SimulateWorkWithResultAsync ( )
117- {
118- return ValueTask . FromResult ( Interlocked . Increment ( ref _counter ));
119- }
148+ // Polly state-based (88 B allocations )
149+ _pollyWithRetry . ExecuteAsync (
150+ static ( state , ct ) => state . SimulateWorkAsync (),
151+ this );
120152```
121153
122154## Recommendations
123155
124156### When to use Foundatio ResiliencePolicy
125157
126- 1 . ** Production workloads with retry policies** : 3-4x faster than Polly when retries are configured
127- 2 . ** Consistent performance requirements** : Overhead is predictable (~ 34-36ns) regardless of configuration
128- 3 . ** High-frequency operations** : Lower overhead matters when called millions of times
158+ 1 . ** Production workloads with retry policies** : 3-5x faster than Polly when retries are configured
159+ 2 . ** Zero-allocation requirements** : State-based overloads achieve true zero-allocation execution
160+ 3 . ** Sync execution** : Native sync methods without async overhead
161+ 4 . ** Consistent performance** : Overhead is predictable (~ 23-39ns) regardless of configuration
162+ 5 . ** High-frequency operations** : Lower overhead matters when called millions of times
129163
130164### When to use Polly
131165
0 commit comments