Windows X64 AMD Ryzen 5 7600X Threading Benchmarks
Machine Profile
Machine Specification
The benchmarks were run on the following machine:
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8655/25H2/2025Update/HudsonValley2)
AMD Ryzen 5 7600X 4.70GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.301
[Host] : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4
.NET 10.0 : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4
Job=.NET 10.0 Runtime=.NET 10.0 Toolchain=net10.0
Alloc Ratio=NA
Note: Results are machine-specific and may vary between systems. Run benchmarks locally for your specific hardware.
BenchmarkDotNet microbenchmarks for all async synchronization primitives in CryptoHives.Foundation.Threading. Benchmarks cover uncontested fast paths (0 other waiters), single-waiter async round trips (1 waiter), and high-contention scenarios (10–100 waiters). All implementations are tested with both a default CancellationToken.None and a non-cancelled CancellationToken to isolate the registration overhead.
Implementations are compared against:
- Pooled — CryptoHives pooled
ValueTask-returning implementation (the baseline, ratio = 1.00) - ProtoPromise — ProtoPromise zero-allocation async primitives
- RefImpl — Simple reference implementation using
TaskCompletionSource<bool>(demonstrates heap-allocating baselines) - Nito.AsyncEx — Nito.AsyncEx async synchronization primitives (Stephen Cleary)
- NeoSmart — NeoSmart.AsyncLock
- VS.Threading / NonKeyed — Microsoft.VisualStudio.Threading / non-keyed async lock variants
- System — .NET built-in synchronization primitives (
SemaphoreSlim,ReaderWriterLockSlim,CountdownEvent,Barrier,ManualResetEventSlim,ManualResetEvent,AutoResetEvent)
Highlights
| Primitive | vs macOS M4 | Key Insight |
|---|---|---|
| AsyncAutoResetEvent (Set) | ~35% slower | System.AutoResetEvent ~4× more expensive than macOS |
| AsyncBarrier (1 participant) | ~63% slower | System.Barrier 2.8× faster than macOS at 10 participants |
| AsyncCountdownEvent (1 participant) | ~38% slower | Near parity with System.CountdownEvent |
| AsyncLock (single) | ~10% slower | ProtoPromise leads; CryptoHives SpinLock ~14× faster than System.SpinLock |
| AsyncManualResetEvent (SetReset) | ~26% slower | ManualResetEvent kernel ~4× more expensive than macOS |
| AsyncRWLock (reader, uncontested) | ~2× slower | Windows faster under contention; inverts at 1+ readers |
| AsyncRWLock (writer, uncontested) | ~52% slower | ProtoPromise leads |
| AsyncSemaphore | ~43% slower | Task continuation scheduling advantage shown in AsTask paths |
Note
On Windows x64, AsTask() continuations are 2–3× faster than on Apple M4 at low contention (1–2 waiters). This reflects the Windows ThreadPool's efficient inline Task continuation scheduling (the posting overhead to the ThreadPool work queue is lower). However, under very high contention (100 waiters) the macOS M4 ValueTask path becomes faster as Windows ThreadPool scheduling saturates. For maximum throughput at all contention levels prefer AsValueTask().
AsyncLock
AsyncLock provides exclusive mutual exclusion with cancellation support. The Single benchmark measures the uncontested acquire+release cycle with no other waiters. The Multiple benchmark adds a configurable number of concurrent contending waiters.
Single (uncontested)
The uncontested benchmark is a proxy for the underlying lock and ValueTask state machine cost when the fast path does not need to suspend. On x64 RyuJIT, interlocked operations (Interlocked.Add, Interlocked.Inc) run sub-nanosecond — faster than ARM64 due to the x86 TSO memory model's more relaxed fence requirements for increment operations.
Key observations:
- Pooled: Fastest async lock; ~10% slower than macOS M4
- ProtoPromise: ~15% faster than Pooled (zero-allocation, no cancellation support)
- System.Lock /
lock(): both ~3–4× cheaper than async paths (no await overhead) - CryptoHives SpinLock: matches System.Lock speed; ~14× faster than System.SpinLock
- Nito and NeoSmart: each allocate per-lock (320 B / 208 B respectively); in the order-of-magnitude slower range
Interlocked.Add/Inc: sub-nanosecond — x64 TSO memory model enables cheaper atomics than ARM64 in this micro-benchmark
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| Lock · Increment · System | 0.0057 ns | 0.001 | - |
| Lock · Interlocked.Add · System | 0.1939 ns | 0.030 | - |
| Lock · Interlocked.Inc · System | 0.1952 ns | 0.030 | - |
| Lock · Interlocked.Exchange · System | 0.5068 ns | 0.078 | - |
| Lock · Interlocked.CmpX · System | 0.8521 ns | 0.132 | - |
| Lock · Lock · System | 3.1394 ns | 0.485 | - |
| Lock · Lock.EnterScope · System | 3.1720 ns | 0.490 | - |
| SpinLock · SpinLock · CryptoHives | 3.3182 ns | 0.513 | - |
| Lock · lock() · System | 3.9953 ns | 0.617 | - |
| LockAsync · AsyncLock · Pooled | 6.4720 ns | 1.000 | - |
| LockAsync · AsyncLock · ProtoPromise | 7.3760 ns | 1.140 | - |
| LockAsync · AsyncSemaphore · VS.Threading | 16.1348 ns | 2.493 | - |
| LockAsync · SemaphoreSlim · System | 16.3141 ns | 2.521 | - |
| LockAsync · AsyncLock · RefImpl | 17.8354 ns | 2.756 | - |
| LockAsync · AsyncLock · NonKeyed | 19.9833 ns | 3.088 | - |
| LockAsync · AsyncLock · Nito.AsyncEx | 39.0541 ns | 6.034 | 320 B |
| SpinWait · SpinOnce · System | 42.1704 ns | 6.516 | - |
| SpinLock · SpinLock · System | 45.3120 ns | 7.001 | - |
| LockAsync · AsyncLock · NeoSmart | 56.5801 ns | 8.742 | 208 B |
Multiple (contended)
Key observations:
- Pooled (ValueTask): Fastest async lock path at all contention levels
- At 1 contender: Pooled (VT) leads; ProtoPromise ~30% slower; SemaphoreSlim ~50% slower
- Pooled (AsTask) at 1 contender: ~2.9× faster than macOS. Windows ThreadPool inlines
Taskcontinuations more efficiently at low concurrency. - At 100 contenders: Pooled (VT) ~29% slower than macOS M4 for the ValueTask path at saturation
- Pooled (AsTask) at 100 contenders with
NotCancelled: Windows ~3.4× faster than macOS for asynchronous task-based paths at high contention
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| Multiple · AsyncLock · Pooled (ValueTask) | 0 | None | 9.524 ns | 1.00 | - |
| Multiple · AsyncLock · Pooled (Task) | 0 | None | 11.026 ns | 1.16 | - |
| Multiple · AsyncLock · ProtoPromise | 0 | None | 11.636 ns | 1.22 | - |
| Multiple · SemaphoreSlim · System | 0 | None | 17.688 ns | 1.86 | - |
| Multiple · AsyncSemaphore · VS.Threading | 0 | None | 18.970 ns | 1.99 | - |
| Multiple · AsyncLock · RefImpl | 0 | None | 19.118 ns | 2.01 | - |
| Multiple · AsyncLock · NonKeyed | 0 | None | 21.376 ns | 2.24 | - |
| Multiple · AsyncLock · Nito | 0 | None | 44.416 ns | 4.66 | 320 B |
| Multiple · AsyncLock · NeoSmart | 0 | None | 58.186 ns | 6.11 | 208 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 0 | NotCancelled | 9.580 ns | 1.00 | - |
| Multiple · AsyncLock · Pooled (Task) | 0 | NotCancelled | 10.904 ns | 1.14 | - |
| Multiple · AsyncLock · ProtoPromise | 0 | NotCancelled | 11.922 ns | 1.24 | - |
| Multiple · SemaphoreSlim · System | 0 | NotCancelled | 17.639 ns | 1.84 | - |
| Multiple · AsyncSemaphore · VS.Threading | 0 | NotCancelled | 19.830 ns | 2.07 | - |
| Multiple · AsyncLock · NonKeyed | 0 | NotCancelled | 22.728 ns | 2.37 | - |
| Multiple · AsyncLock · Nito | 0 | NotCancelled | 39.177 ns | 4.09 | 320 B |
| Multiple · AsyncLock · NeoSmart | 0 | NotCancelled | 58.758 ns | 6.13 | 208 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 1 | None | 30.312 ns | 1.00 | - |
| Multiple · AsyncLock · ProtoPromise | 1 | None | 38.128 ns | 1.26 | - |
| Multiple · SemaphoreSlim · System | 1 | None | 43.053 ns | 1.42 | 88 B |
| Multiple · AsyncSemaphore · VS.Threading | 1 | None | 71.197 ns | 2.35 | 168 B |
| Multiple · AsyncLock · RefImpl | 1 | None | 76.606 ns | 2.53 | 216 B |
| Multiple · AsyncLock · Nito | 1 | None | 98.038 ns | 3.23 | 728 B |
| Multiple · AsyncLock · NeoSmart | 1 | None | 118.103 ns | 3.90 | 416 B |
| Multiple · AsyncLock · Pooled (Task) | 1 | None | 467.260 ns | 15.42 | 272 B |
| Multiple · AsyncLock · NonKeyed | 1 | None | 537.478 ns | 17.73 | 352 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 1 | NotCancelled | 48.928 ns | 1.00 | - |
| Multiple · AsyncLock · ProtoPromise | 1 | NotCancelled | 68.091 ns | 1.39 | - |
| Multiple · AsyncSemaphore · VS.Threading | 1 | NotCancelled | 82.251 ns | 1.68 | 168 B |
| Multiple · AsyncLock · NeoSmart | 1 | NotCancelled | 121.842 ns | 2.49 | 416 B |
| Multiple · AsyncLock · Nito | 1 | NotCancelled | 381.676 ns | 7.80 | 968 B |
| Multiple · AsyncLock · Pooled (Task) | 1 | NotCancelled | 513.639 ns | 10.50 | 272 B |
| Multiple · SemaphoreSlim · System | 1 | NotCancelled | 597.392 ns | 12.21 | 504 B |
| Multiple · AsyncLock · NonKeyed | 1 | NotCancelled | 696.758 ns | 14.24 | 640 B |
| Multiple · AsyncLock · ProtoPromise | 10 | None | 270.858 ns | 0.81 | - |
| Multiple · SemaphoreSlim · System | 10 | None | 283.837 ns | 0.85 | 880 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 10 | None | 335.026 ns | 1.00 | - |
| Multiple · AsyncSemaphore · VS.Threading | 10 | None | 526.298 ns | 1.57 | 1680 B |
| Multiple · AsyncLock · Nito | 10 | None | 553.332 ns | 1.65 | 4400 B |
| Multiple · AsyncLock · NeoSmart | 10 | None | 635.329 ns | 1.90 | 2288 B |
| Multiple · AsyncLock · RefImpl | 10 | None | 654.051 ns | 1.95 | 2160 B |
| Multiple · AsyncLock · Pooled (Task) | 10 | None | 3,158.086 ns | 9.43 | 1352 B |
| Multiple · AsyncLock · NonKeyed | 10 | None | 3,455.383 ns | 10.31 | 2296 B |
| Multiple · AsyncLock · ProtoPromise | 10 | NotCancelled | 496.349 ns | 0.80 | - |
| Multiple · AsyncLock · Pooled (ValueTask) | 10 | NotCancelled | 623.256 ns | 1.00 | - |
| Multiple · AsyncLock · NeoSmart | 10 | NotCancelled | 642.343 ns | 1.03 | 2288 B |
| Multiple · AsyncSemaphore · VS.Threading | 10 | NotCancelled | 734.316 ns | 1.18 | 1680 B |
| Multiple · AsyncLock · Nito | 10 | NotCancelled | 3,224.239 ns | 5.17 | 6800 B |
| Multiple · AsyncLock · Pooled (Task) | 10 | NotCancelled | 3,366.491 ns | 5.40 | 1352 B |
| Multiple · SemaphoreSlim · System | 10 | NotCancelled | 4,357.466 ns | 6.99 | 3888 B |
| Multiple · AsyncLock · NonKeyed | 10 | NotCancelled | 5,054.288 ns | 8.11 | 5176 B |
| Multiple · SemaphoreSlim · System | 100 | None | 2,538.924 ns | 0.80 | 8800 B |
| Multiple · AsyncLock · ProtoPromise | 100 | None | 2,616.672 ns | 0.82 | - |
| Multiple · AsyncLock · Pooled (ValueTask) | 100 | None | 3,179.786 ns | 1.00 | - |
| Multiple · AsyncSemaphore · VS.Threading | 100 | None | 4,894.898 ns | 1.54 | 21120 B |
| Multiple · AsyncLock · Nito | 100 | None | 5,325.309 ns | 1.67 | 41120 B |
| Multiple · AsyncLock · NeoSmart | 100 | None | 5,895.268 ns | 1.85 | 21008 B |
| Multiple · AsyncLock · RefImpl | 100 | None | 6,318.729 ns | 1.99 | 21600 B |
| Multiple · AsyncLock · Pooled (Task) | 100 | None | 33,600.780 ns | 10.57 | 12216 B |
| Multiple · AsyncLock · NonKeyed | 100 | None | 35,603.366 ns | 11.20 | 21800 B |
| Multiple · AsyncLock · ProtoPromise | 100 | NotCancelled | 4,689.983 ns | 0.87 | - |
| Multiple · AsyncLock · Pooled (ValueTask) | 100 | NotCancelled | 5,360.730 ns | 1.00 | - |
| Multiple · AsyncLock · NeoSmart | 100 | NotCancelled | 5,968.076 ns | 1.11 | 21008 B |
| Multiple · AsyncSemaphore · VS.Threading | 100 | NotCancelled | 7,014.726 ns | 1.31 | 21120 B |
| Multiple · AsyncLock · Nito | 100 | NotCancelled | 31,183.481 ns | 5.82 | 65120 B |
| Multiple · AsyncLock · Pooled (Task) | 100 | NotCancelled | 35,534.963 ns | 6.63 | 12216 B |
| Multiple · SemaphoreSlim · System | 100 | NotCancelled | 44,303.836 ns | 8.27 | 37792 B |
| Multiple · AsyncLock · NonKeyed | 100 | NotCancelled | 54,064.472 ns | 10.09 | 50600 B |
AsyncAutoResetEvent
AsyncAutoResetEvent releases exactly one waiter per Set() call. Three benchmarks cover: Set (no waiters — measures pure signal cost), SetThenWait (signal then immediately wait on the newly-reset event), and WaitThenSet (N waiters await then are unblocked one by one).
Set (no waiters)
Key observations:
- Pooled and ProtoPromise: sub-nanosecond pure-signal cost
- System.AutoResetEvent (kernel): ~320× slower than Pooled; ~4× more expensive than macOS — Windows kernel event objects carry a significantly larger overhead than Apple's
mach_semaphore
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| Set · AsyncAutoReset · ProtoPromise | 0.5506 ns | 0.73 | - |
| Set · AsyncAutoReset · Pooled | 0.7530 ns | 1.00 | - |
| Set · AsyncAutoReset · Nito.AsyncEx | 4.4771 ns | 5.95 | - |
| Set · AsyncAutoReset · RefImpl | 4.5253 ns | 6.02 | - |
| Set · AutoResetEvent · System | 215.6038 ns | 286.67 | - |
SetThenWait
Signals then immediately calls WaitAsync. Because the event was just set, WaitAsync returns synchronously — measures the combined signal + synchronous check cost.
Key observations:
- Pooled (ValueTask) and ProtoPromise: sub-10 ns combined signal + synchronous check
- RefImpl and Nito: ~2× slower than Pooled;
SetThenWaitfor RefImpl is slower here than macOS because Windows'sTaskCompletionSourcefast-path post has more overhead
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| SetThenWait · AsyncAutoReset · ProtoPromise | 5.450 ns | 0.90 | - |
| SetThenWait · AsyncAutoReset · Pooled (ValueTask) | 6.083 ns | 1.00 | - |
| SetThenWait · AsyncAutoReset · Pooled (AsTask) | 8.694 ns | 1.43 | - |
| SetThenWait · AsyncAutoReset · Nito.AsyncEx | 14.165 ns | 2.33 | - |
| SetThenWait · AsyncAutoReset · RefImpl | 15.447 ns | 2.54 | - |
WaitThenSet
N waiters call WaitAsync, then Set() is called N times from another context. Each Set/Wait round trip involves a full async scheduling cycle (suspend + resume).
Key observations:
- Pooled (AsValueTask) at 1 waiter: ~25% slower than macOS; the continuation dispatch overhead through
ValueTaskpooling is slightly more expensive on x64 vs ARM64 for single-waiter - Pooled (AsTask) at 1 waiter: ~2.75× faster than macOS for the Task path; ThreadPool Task scheduling is faster at low concurrency on Windows
- At 100 waiters
None: Pooled (VT) ~25% slower than macOS for the ValueTask path at saturation - Nito at 100 waiters
NotCancelled: tens of microseconds — scales linearly with per-waiter CancellationToken registration
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| WaitThenSet · AsyncAutoReset · ProtoPromise | 1 | None | 23.66 ns | 0.83 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 1 | None | 25.86 ns | 0.90 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 1 | None | 26.07 ns | 0.91 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 1 | None | 28.53 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 1 | None | 28.63 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 1 | None | 29.02 ns | 1.01 | 96 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 1 | None | 35.46 ns | 1.24 | 160 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 1 | None | 42.84 ns | 1.50 | 80 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 1 | None | 449.80 ns | 15.71 | 231 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 1 | NotCancelled | 43.33 ns | 0.99 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 1 | NotCancelled | 43.54 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 1 | NotCancelled | 43.71 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 1 | NotCancelled | 46.97 ns | 1.07 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 1 | NotCancelled | 66.61 ns | 1.52 | 80 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 1 | NotCancelled | 77.09 ns | 1.76 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 1 | NotCancelled | 337.09 ns | 7.71 | 400 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 1 | NotCancelled | 514.33 ns | 11.77 | 232 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 2 | None | 46.85 ns | 0.66 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 2 | None | 52.64 ns | 0.74 | 192 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 2 | None | 63.24 ns | 0.89 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 2 | None | 66.85 ns | 0.94 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 2 | None | 67.80 ns | 0.95 | 320 B |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 2 | None | 71.21 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 2 | None | 72.04 ns | 1.01 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 2 | None | 101.12 ns | 1.42 | 160 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 2 | None | 745.18 ns | 10.47 | 343 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 2 | NotCancelled | 91.95 ns | 0.90 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 2 | NotCancelled | 102.11 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 2 | NotCancelled | 102.94 ns | 1.01 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 2 | NotCancelled | 104.05 ns | 1.02 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 2 | NotCancelled | 106.53 ns | 1.04 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 2 | NotCancelled | 146.62 ns | 1.44 | 160 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 2 | NotCancelled | 575.66 ns | 5.64 | 800 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 2 | NotCancelled | 903.74 ns | 8.85 | 344 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 10 | None | 245.19 ns | 0.69 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 10 | None | 281.16 ns | 0.79 | 960 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 10 | None | 340.86 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 10 | None | 349.20 ns | 0.98 | 1600 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 10 | None | 349.23 ns | 0.98 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 10 | None | 356.04 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 10 | None | 372.92 ns | 1.05 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 10 | None | 533.28 ns | 1.50 | 800 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 10 | None | 2,115.63 ns | 5.94 | 1237 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 10 | NotCancelled | 458.40 ns | 0.83 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 10 | NotCancelled | 535.05 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 10 | NotCancelled | 545.01 ns | 0.98 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 10 | NotCancelled | 549.83 ns | 0.99 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 10 | NotCancelled | 554.84 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 10 | NotCancelled | 768.56 ns | 1.39 | 800 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 10 | NotCancelled | 2,908.98 ns | 5.24 | 4000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 10 | NotCancelled | 3,374.46 ns | 6.08 | 1239 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 100 | None | 2,267.45 ns | 0.65 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 100 | None | 2,767.11 ns | 0.79 | 9600 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 100 | None | 3,149.15 ns | 0.90 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 100 | None | 3,185.54 ns | 0.91 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 100 | None | 3,508.92 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 100 | None | 3,532.61 ns | 1.01 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 100 | None | 3,555.60 ns | 1.01 | 16000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 100 | None | 5,238.56 ns | 1.49 | 8000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 100 | None | 16,210.37 ns | 4.62 | 11320 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 100 | NotCancelled | 4,746.20 ns | 0.90 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 100 | NotCancelled | 5,263.17 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 100 | NotCancelled | 5,327.80 ns | 1.01 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 100 | NotCancelled | 5,349.26 ns | 1.02 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 100 | NotCancelled | 5,645.75 ns | 1.07 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 100 | NotCancelled | 7,803.78 ns | 1.48 | 8000 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 100 | NotCancelled | 28,920.30 ns | 5.50 | 40000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 100 | NotCancelled | 290,521.43 ns | 55.20 | 11324 B |
AsyncManualResetEvent
AsyncManualResetEvent releases all waiters when set and stays set until explicitly reset. The SetReset benchmark measures the rapid set/reset cycle. SetThenWait measures a set followed by a synchronous wait. WaitThenSet drives N concurrent waiters through a broadcast release.
SetReset
Key observations:
- Pooled: ~2.8× faster than
ManualResetEventSlim.Set+Reset - ManualResetEvent (kernel): ~210× slower than Pooled; ~4× more expensive than macOS — Windows kernel event objects are significantly heavier
- ProtoPromise leads at the lowest absolute cost
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| SetReset · AsyncManualReset · ProtoPromise | 1.447 ns | 0.71 | - |
| SetReset · AsyncManualReset · Pooled | 2.050 ns | 1.00 | - |
| SetReset · ManualResetEventSlim · System | 5.438 ns | 2.65 | - |
| SetReset · AsyncManualReset · RefImpl | 9.979 ns | 4.87 | 96 B |
| SetReset · AsyncManualReset · Nito.AsyncEx | 17.013 ns | 8.30 | 96 B |
| SetReset · ManualResetEvent · System | 432.047 ns | 210.74 | - |
SetThenWait
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| SetThenWait · AsyncManualReset · ProtoPromise | 5.864 ns | 0.64 | - |
| SetThenWait · AsyncManualReset · Pooled (ValueTask) | 9.203 ns | 1.00 | - |
| SetThenWait · AsyncManualReset · Pooled (AsTask) | 9.251 ns | 1.01 | - |
| SetThenWait · AsyncManualReset · RefImpl | 13.676 ns | 1.49 | 96 B |
| SetThenWait · AsyncManualReset · Nito.AsyncEx | 24.142 ns | 2.62 | 96 B |
WaitThenSet
Because AsyncManualResetEvent broadcasts to all waiters from a single Set(), RefImpl uses one TaskCompletionSource shared across all waiters — its allocation stays constant at 96 B regardless of waiter count. This gives it an unusual cost profile.
Key observations:
- Pooled (AsValueTask) at 1 waiter: ~36% slower than macOS; this is one of the scenarios where the x64 ValueTask dispatch is slower than ARM64
- Pooled (AsTask) at 1 waiter: ~2.9× faster than macOS for Task path
- At 100 waiters
None: Pooled (VT) ~30% slower than macOS; broadcast release at high contention favours Apple Silicon - Nito at 100 waiters
NotCancelled: tens of microseconds — Windows ~23% faster than macOS; the NitoCancellationToken.Registerscales with thread count and Windows ThreadPool completes registrations faster
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| WaitThenSet · AsyncManualReset · RefImpl | 1 | None | 21.31 ns | 0.73 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 1 | None | 26.05 ns | 0.90 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 1 | None | 26.90 ns | 0.93 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 1 | None | 27.19 ns | 0.94 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 1 | None | 28.86 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 1 | None | 29.00 ns | 1.00 | 96 B |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 1 | None | 29.01 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 1 | None | 41.05 ns | 1.42 | 80 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 1 | None | 439.28 ns | 15.14 | 231 B |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 1 | NotCancelled | 43.70 ns | 0.96 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 1 | NotCancelled | 44.63 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 1 | NotCancelled | 45.36 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 1 | NotCancelled | 45.72 ns | 1.01 | - |
| WaitThenSet · AsyncManualReset · ProtoPromise | 1 | NotCancelled | 47.92 ns | 1.06 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 1 | NotCancelled | 63.31 ns | 1.40 | 80 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 1 | NotCancelled | 487.66 ns | 10.75 | 232 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 1 | NotCancelled | 619.69 ns | 13.66 | 808 B |
| WaitThenSet · AsyncManualReset · RefImpl | 2 | None | 24.12 ns | 0.37 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 2 | None | 38.01 ns | 0.58 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 2 | None | 45.85 ns | 0.70 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 2 | None | 59.82 ns | 0.92 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 2 | None | 60.29 ns | 0.92 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 2 | None | 65.21 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 2 | None | 65.37 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 2 | None | 91.73 ns | 1.40 | 160 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 2 | None | 769.23 ns | 11.77 | 344 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 2 | NotCancelled | 89.35 ns | 0.88 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 2 | NotCancelled | 93.97 ns | 0.92 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 2 | NotCancelled | 99.86 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 2 | NotCancelled | 101.01 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 2 | NotCancelled | 101.79 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 2 | NotCancelled | 140.33 ns | 1.38 | 160 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 2 | NotCancelled | 900.51 ns | 8.85 | 344 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 2 | NotCancelled | 1,075.26 ns | 10.56 | 1488 B |
| WaitThenSet · AsyncManualReset · RefImpl | 10 | None | 65.32 ns | 0.18 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 10 | None | 111.64 ns | 0.31 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 10 | None | 213.75 ns | 0.60 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 10 | None | 319.12 ns | 0.90 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 10 | None | 322.67 ns | 0.91 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 10 | None | 351.34 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 10 | None | 354.82 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 10 | None | 486.56 ns | 1.37 | 800 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 10 | None | 2,053.86 ns | 5.79 | 1239 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 10 | NotCancelled | 426.86 ns | 0.81 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 10 | NotCancelled | 518.51 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 10 | NotCancelled | 527.70 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 10 | NotCancelled | 531.19 ns | 1.01 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 10 | NotCancelled | 552.29 ns | 1.05 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 10 | NotCancelled | 725.76 ns | 1.38 | 800 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 10 | NotCancelled | 2,835.69 ns | 5.37 | 1240 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 10 | NotCancelled | 3,459.05 ns | 6.56 | 6464 B |
| WaitThenSet · AsyncManualReset · RefImpl | 100 | None | 545.01 ns | 0.16 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 100 | None | 929.35 ns | 0.27 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 100 | None | 2,117.15 ns | 0.62 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 100 | None | 3,064.17 ns | 0.89 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 100 | None | 3,078.54 ns | 0.90 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 100 | None | 3,435.63 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 100 | None | 3,459.37 ns | 1.01 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 100 | None | 4,804.34 ns | 1.40 | 8000 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 100 | None | 15,421.83 ns | 4.49 | 11320 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 100 | NotCancelled | 4,210.12 ns | 0.81 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 100 | NotCancelled | 5,195.50 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 100 | NotCancelled | 5,212.95 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 100 | NotCancelled | 5,218.04 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 100 | NotCancelled | 5,247.35 ns | 1.01 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 100 | NotCancelled | 7,302.66 ns | 1.40 | 8000 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 100 | NotCancelled | 128,084.00 ns | 24.57 | 61615 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 100 | NotCancelled | 309,059.35 ns | 59.29 | 11326 B |
AsyncSemaphore
AsyncSemaphore manages a counted resource permit (here initialized to 1). The single-permit benchmark measures the wait+release cycle uncontested (the permit is available on entry).
Key observations:
- Pooled: ~43% slower than macOS for the uncontested path
- ProtoPromise: leads across all configurations
- System.SemaphoreSlim: ~85% slower than Pooled; macOS equivalent is ~25% cheaper due to lighter kernel semaphore
- RefImpl: Windows
TaskCompletionSourcecreation is ~55% more expensive than on macOS
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| WaitRelease · AsyncSemaphore · ProtoPromise | 6.398 ns | 0.70 | - |
| WaitRelease · AsyncSemaphore · Pooled | 9.083 ns | 1.00 | - |
| WaitRelease · AsyncSemaphore · Nito.AsyncEx | 14.655 ns | 1.61 | - |
| WaitRelease · SemaphoreSlim · System | 16.827 ns | 1.85 | - |
| WaitRelease · AsyncSemaphore · RefImpl | 17.893 ns | 1.97 | - |
AsyncCountdownEvent
AsyncCountdownEvent completes when signaled N times. Two scenarios are benchmarked: SignalAndWait where the last signal also observes the countdown reaching zero, and WaitAndSignal where a waiter is already blocking.
Key observations:
- Pooled at P=1: ~38% slower than macOS
- System.CountdownEvent: virtually identical on both platforms; kernel countdown objects normalize across architectures
- At P=10: Pooled near-identical on both platforms; contention equalizes them
- ProtoPromise consistently leads or matches System.CountdownEvent at both P=1 and P=10
| Description | ParticipantCount | Mean | Ratio | Allocated |
|---|---|---|---|---|
| SignalAndWait · CountdownEvent · System | 1 | 6.767 ns | 0.89 | - |
| SignalAndWait · AsyncCountdownEv · Pooled | 1 | 7.574 ns | 1.00 | - |
| SignalAndWait · AsyncCountdownEv · ProtoPromise | 1 | 7.588 ns | 1.00 | - |
| SignalAndWait · AsyncCountdownEv · RefImpl | 1 | 16.461 ns | 2.17 | 96 B |
| WaitAndSignal · AsyncCountdownEv · ProtoPromise | 1 | 18.802 ns | 2.48 | - |
| WaitAndSignal · AsyncCountdownEv · Pooled | 1 | 49.150 ns | 6.49 | - |
| SignalAndWait · AsyncCountdownEv · ProtoPromise | 10 | 17.544 ns | 0.76 | - |
| SignalAndWait · CountdownEvent · System | 10 | 20.060 ns | 0.87 | - |
| SignalAndWait · AsyncCountdownEv · Pooled | 10 | 23.027 ns | 1.00 | - |
| SignalAndWait · AsyncCountdownEv · RefImpl | 10 | 28.830 ns | 1.25 | 96 B |
| WaitAndSignal · AsyncCountdownEv · ProtoPromise | 10 | 29.128 ns | 1.26 | - |
| WaitAndSignal · AsyncCountdownEv · Pooled | 10 | 64.808 ns | 2.81 | - |
AsyncBarrier
AsyncBarrier blocks all N participants until all have signaled, then releases them simultaneously.
Key observations:
- Pooled at P=1: ~63% slower than macOS; largest uncontested gap across all primitives
- System.Barrier at P=1: ~42× slower than Pooled; ~2.4× faster than macOS —
System.Barrieruses a spin-wait strategy that performs better on x64 than macOS at low participant counts - At P=10: Pooled ~30% slower than macOS; System.Barrier ~2.8× slower on Windows at P=10 — the x86 spin-wait amplifies context switching cost as participant count grows
| Description | ParticipantCount | Mean | Ratio | Allocated |
|---|---|---|---|---|
| SignalAndWait · AsyncBarrier · Pooled | 1 | 11.34 ns | 1.00 | - |
| SignalAndWait · Barrier · System | 1 | 440.59 ns | 38.86 | 238 B |
| SignalAndWait · AsyncBarrier · RefImpl | 1 | 966.04 ns | 85.21 | 8356 B |
| SignalAndWait · AsyncBarrier · Pooled | 10 | 268.81 ns | 1.00 | - |
| SignalAndWait · AsyncBarrier · RefImpl | 10 | 1,598.64 ns | 5.95 | 10032 B |
| SignalAndWait · Barrier · System | 10 | 4,645.62 ns | 17.28 | 1392 B |
AsyncReaderWriterLock
AsyncReaderWriterLock supports multiple concurrent readers or a single exclusive writer, plus an upgradeable reader that can atomically promote to writer.
WriterLock (uncontested)
Key observations:
- Pooled: ~52% slower than macOS; the writer lock has more bookkeeping than simple exclusion locks, amplifying the ARM64 JIT advantage
- ProtoPromise: leads Pooled (~20% faster)
- System.ReaderWriterLockSlim: ~½ the cost of Pooled; ~50% slower than macOS equivalent
- VS.Threading: async overhead model (~100× vs Pooled); ~30% faster than macOS
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| WriterLock · RWLockSlim · System | 6.931 ns | 0.62 | - |
| WriterLock · AsyncRWLock · Proto.Promises | 8.353 ns | 0.75 | - |
| WriterLock · AsyncRWLock · Pooled | 11.103 ns | 1.00 | - |
| WriterLock · AsyncRWLock · RefImpl | 18.529 ns | 1.67 | - |
| WriterLock · AsyncRWLock · Nito.AsyncEx | 55.739 ns | 5.02 | 496 B |
| WriterLock · AsyncRWLock · VS.Threading | 1,044.066 ns | 94.04 | 584 B |
ReaderLock
The reader lock benchmark shows a contention inversion from macOS:
- Uncontested (0 concurrent readers): macOS ~2× faster; the uncontested reader lock path on ARM64 is exceptionally efficient
- 1 concurrent reader: Windows ~32% faster with a single contending reader; the Windows ThreadPool schedules the read-lock resume continuation faster at low concurrency
- 100 concurrent readers: Windows ~2× faster at high contention; the Pooled reader lock's internal structure for multi-reader scheduling benefits from the Windows ThreadPool's scalable task dispatching
- ProtoPromise at 100 readers: dramatically different scaling ratios between platforms; macOS ProtoPromise handles the broadcast release with far fewer overheads
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| ReaderLock · RWLockSlim · System | 0 | None | 7.728 ns | 0.49 | - |
| ReaderLock · AsyncRWLock · Pooled | 0 | None | 15.803 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 0 | None | 18.267 ns | 1.16 | - |
| ReaderLock · AsyncRWLock · RefImpl | 0 | None | 18.839 ns | 1.19 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 0 | None | 40.865 ns | 2.59 | 320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 0 | None | 224.367 ns | 14.20 | 208 B |
| ReaderLock · AsyncRWLock · Pooled | 0 | NotCancelled | 15.951 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 18.197 ns | 1.14 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 0 | NotCancelled | 40.386 ns | 2.53 | 320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 225.331 ns | 14.13 | 208 B |
| ReaderLock · RWLockSlim · System | 1 | None | 12.326 ns | 0.30 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 1 | None | 28.524 ns | 0.69 | - |
| ReaderLock · AsyncRWLock · RefImpl | 1 | None | 33.435 ns | 0.80 | - |
| ReaderLock · AsyncRWLock · Pooled | 1 | None | 41.565 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 1 | None | 83.736 ns | 2.01 | 640 B |
| ReaderLock · AsyncRWLock · VS.Threading | 1 | None | 531.027 ns | 12.78 | 416 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 28.778 ns | 0.71 | - |
| ReaderLock · AsyncRWLock · Pooled | 1 | NotCancelled | 40.399 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 1 | NotCancelled | 81.648 ns | 2.02 | 640 B |
| ReaderLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 532.178 ns | 13.17 | 416 B |
| ReaderLock · RWLockSlim · System | 10 | None | 61.963 ns | 0.32 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 10 | None | 141.332 ns | 0.73 | - |
| ReaderLock · AsyncRWLock · RefImpl | 10 | None | 143.397 ns | 0.74 | - |
| ReaderLock · AsyncRWLock · Pooled | 10 | None | 194.403 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 10 | None | 467.103 ns | 2.40 | 3520 B |
| ReaderLock · AsyncRWLock · VS.Threading | 10 | None | 3,642.898 ns | 18.74 | 2288 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 10 | NotCancelled | 139.359 ns | 0.72 | - |
| ReaderLock · AsyncRWLock · Pooled | 10 | NotCancelled | 194.276 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 10 | NotCancelled | 464.283 ns | 2.39 | 3520 B |
| ReaderLock · AsyncRWLock · VS.Threading | 10 | NotCancelled | 3,640.725 ns | 18.74 | 2288 B |
| ReaderLock · RWLockSlim · System | 100 | None | 570.326 ns | 0.33 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 100 | None | 1,223.857 ns | 0.70 | - |
| ReaderLock · AsyncRWLock · RefImpl | 100 | None | 1,249.889 ns | 0.71 | - |
| ReaderLock · AsyncRWLock · Pooled | 100 | None | 1,748.960 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 100 | None | 4,435.991 ns | 2.54 | 32320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 100 | None | 86,096.879 ns | 49.23 | 21008 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 100 | NotCancelled | 1,245.661 ns | 0.71 | - |
| ReaderLock · AsyncRWLock · Pooled | 100 | NotCancelled | 1,743.212 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 100 | NotCancelled | 4,404.138 ns | 2.53 | 32320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 100 | NotCancelled | 87,835.002 ns | 50.39 | 21008 B |
UpgradeableReaderLock
Same inversion pattern as the plain reader lock:
- Uncontested: macOS ~2× faster
- 1–2 concurrent upgradeables: Windows is faster (~10–37%)
- VS.Threading upgradeable: allocates 616 B per acquire; slowest by far at all contention levels
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| UpgradeableReaderLock · RWLockSlim · System | 0 | None | 6.741 ns | 0.41 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 0 | None | 16.248 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 0 | None | 19.423 ns | 1.20 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 0 | None | 1,064.281 ns | 65.50 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 0 | NotCancelled | 16.334 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 17.778 ns | 1.09 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 1,135.176 ns | 69.50 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 1 | None | 6.725 ns | 0.34 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 1 | None | 17.560 ns | 0.90 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 1 | None | 19.551 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 1 | None | 1,048.830 ns | 53.65 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 17.601 ns | 0.92 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 1 | NotCancelled | 19.051 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 1,181.340 ns | 62.01 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 2 | None | 6.733 ns | 0.36 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 2 | None | 17.646 ns | 0.94 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 2 | None | 18.809 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 2 | None | 1,045.428 ns | 55.58 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 2 | NotCancelled | 17.493 ns | 0.90 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 2 | NotCancelled | 19.365 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 2 | NotCancelled | 1,179.300 ns | 60.90 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 5 | None | 24.080 ns | 0.31 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 5 | None | 52.959 ns | 0.69 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 5 | None | 76.546 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 5 | None | 2,477.664 ns | 32.37 | 1240 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 5 | NotCancelled | 53.909 ns | 0.72 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 5 | NotCancelled | 74.742 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 5 | NotCancelled | 2,586.760 ns | 34.61 | 1240 B |
UpgradedWriterLock
The upgraded writer acquires an upgradeable read lock then promotes to exclusive writer, draining all active readers first.
- Uncontested: macOS ~32% faster
- 1 concurrent upgraded writer: Windows ~30% faster (same inversion as Reader at 1 contender); the Windows ThreadPool inline-completes the queued upgrade faster
- 5 concurrent upgraded writers: Windows ~2.5× faster; the writer upgrade queue is the starkest scenario where Windows ThreadPool's Task scheduling advantage over macOS is most visible
| Description | Iterations | cancellationType | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| UpgradedWriterLock · RWLockSlim · System | 0 | None | 13.49 ns | 0.53 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 0 | None | 23.76 ns | 0.93 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 0 | None | 25.64 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 0 | None | 1,716.69 ns | 66.97 | 824 B |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 24.74 ns | 0.97 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 0 | NotCancelled | 25.37 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 1,785.84 ns | 70.39 | 824 B |
| UpgradedWriterLock · RWLockSlim · System | 1 | None | 34.65 ns | 0.63 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 1 | None | 44.20 ns | 0.81 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 1 | None | 54.81 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 1 | None | 2,233.12 ns | 40.74 | 1032 B |
| UpgradedWriterLock · AsyncRWLock · Pooled | 1 | NotCancelled | 65.71 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 69.90 ns | 1.06 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 2,269.89 ns | 34.55 | 1032 B |
| UpgradedWriterLock · RWLockSlim · System | 2 | None | 25.47 ns | 0.33 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 2 | None | 53.43 ns | 0.69 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 2 | None | 77.54 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 2 | None | 2,717.26 ns | 35.04 | 1240 B |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 2 | NotCancelled | 80.98 ns | 0.86 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 2 | NotCancelled | 94.63 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 2 | NotCancelled | 2,780.83 ns | 29.39 | 1240 B |
| UpgradedWriterLock · RWLockSlim · System | 5 | None | 43.55 ns | 0.31 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 5 | None | 91.85 ns | 0.66 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 5 | None | 139.13 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 5 | None | 4,235.82 ns | 30.45 | 1864 B |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 5 | NotCancelled | 116.29 ns | 0.80 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 5 | NotCancelled | 146.08 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 5 | NotCancelled | 4,381.78 ns | 30.00 | 1864 B |