Skip to main content

Benchmarks

Kevlar vs Polly v8 across every strategy, measured with BenchmarkDotNet on GitHub Actions and republished automatically by the benchmarks workflow.

Last updated 2026-08-21 23:26 UTC (commit ab37156).

note

Microbenchmarks on shared CI runners are noisy. The vs Polly column is the median ratio over the most recent runs (up to 10); anything within ±20% is reported as on par. Absolute times move with runner hardware — the ratios are the signal.

Pipeline overhead floor

What an empty pipeline costs per execution — the fixed tax every strategy builds on.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Empty pipeline — async13.2 ns54.5 ns0 B0 B3.2× faster
Empty pipeline — reference-state baseline10.4 ns0 B
Empty pipeline — caller-seeded context101 ns0 B
EmptyOutcomeState7.2 ns0 B
EmptyTaskOutcomeState11.3 ns0 B
Empty pipeline — zero-closure state overload10.3 ns55.9 ns0 B0 B4.4× faster
Empty pipeline — sync7.6 ns35.6 ns0 B0 B4.1× faster

Retry

Happy path (judge overhead only) and a recovery path where every call fails twice before succeeding, with backoff disabled so only strategy machinery is measured.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Retry(3) — success on first attempt105 ns212 ns0 B24 B1.6× faster
Retry(3) — two failures then success3.59 μs3.96 μs192 B328 Bon par

Timeout

The timeout never fires; this is the cost of arming and disarming the cancellation plumbing on every call.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Timeout(10 s) — completes instantly202 ns199 ns0 B0 Bon par
SynchronousGenerator_HappyPath201 ns0 B
AsynchronousGenerator_HappyPath2.06 μs695 B
AsyncHookConfigured_HappyPath203 ns0 B

Circuit breaker

Ratio/sampling bookkeeping while closed, and the fast-fail rejection cost while manually isolated (thrown exception included).

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Isolated circuit — fast-fail rejection4.94 μs5.04 μs1.3 KB1.3 KBon par
Ratio breaker, closed — success207 ns243 ns0 B24 Bon par
DynamicDurationConfigured222 ns0 B
AsyncCallbackConfigured220 ns0 B

Fallback

Pass-through when the execution succeeds, and substitution when it throws.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
NoNotification2.18 μs200 B
SyncNotification2.14 μs200 B
CompletedAsyncNotification2.14 μs200 B
YieldingAsyncNotification5.69 μs854 B
Fallback — not triggered116 ns114 ns0 B0 Bon par
Fallback — triggered by exception2.18 μs2.27 μs200 B256 Bon par

Rate limit

Uncontended token-bucket permit acquisition — every call is admitted.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Token bucket — uncontended acquire142 ns142 ns0 B0 Bon par
WithHooks_Uncontended149 ns0 B
FrameworkAdapter_Uncontended137 ns0 B
PartitionedFrameworkAdapter_Uncontended164 ns32 B

Concurrency limit

A single caller against a large permit count; acquire/release cost with no queueing.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Concurrency limit — uncontended144 ns192 ns0 B40 Bon par
WithHooks_Uncontended154 ns0 B

Typed result handling

Retry configured to treat a sentinel result as a failure; the returned value never matches, so this is the per-call cost of judging results.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Typed retry — result judged, no retry112 ns160 ns0 B0 B1.2× faster

Composed pipelines

How per-call overhead scales with pipeline depth when nothing goes wrong.

ScenarioKevlarPollyKevlar allocatedPolly allocatedKevlar vs Polly
Timeout → Retry → ratio breaker356 ns647 ns0 B48 B1.8× faster
Token bucket → Timeout → Retry → ratio breaker → Concurrency limit504 ns960 ns0 B88 B1.9× faster

Environment

  • Intel Xeon Platinum 8370C CPU 2.80GHz
  • .NET 10.0.11 (10.0.11, 10.0.1126.37416), BenchmarkDotNet 0.15.8
  • Times are medians; allocations are per operation.

Reproduce

dotnet run -c Release --project benchmarks/Kevlar.Benchmarks -- --filter '*'

As always with microbenchmarks: measure your own workload before optimizing around these numbers. Nanosecond differences matter in tight loops and high-throughput services; they don't matter around a 50 ms network call.