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.8037/25H2/2025Update/HudsonValley2)
AMD Ryzen 5 7600X 4.70GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.201
[Host] : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v4
.NET 10.0 : .NET 10.0.5 (10.0.5, 10.0.526.15411), 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.0010 ns | 0.000 | - |
| Lock · Interlocked.Add · System | 0.1764 ns | 0.021 | - |
| Lock · Interlocked.Inc · System | 0.1925 ns | 0.023 | - |
| Lock · Interlocked.Exchange · System | 0.5166 ns | 0.062 | - |
| Lock · Interlocked.CmpX · System | 0.8476 ns | 0.102 | - |
| Lock · Lock · System | 3.0566 ns | 0.368 | - |
| Lock · Lock.EnterScope · System | 3.1459 ns | 0.379 | - |
| SpinLock · SpinLock · CryptoHives | 3.2782 ns | 0.395 | - |
| Lock · lock() · System | 4.0113 ns | 0.483 | - |
| LockAsync · AsyncLock · ProtoPromise | 7.0620 ns | 0.851 | - |
| LockAsync · AsyncLock · Pooled | 8.3011 ns | 1.000 | - |
| LockAsync · AsyncSemaphore · VS.Threading | 15.7214 ns | 1.894 | - |
| LockAsync · SemaphoreSlim · System | 16.4273 ns | 1.979 | - |
| LockAsync · AsyncLock · RefImpl | 17.8237 ns | 2.147 | - |
| LockAsync · AsyncLock · NonKeyed | 19.4962 ns | 2.349 | - |
| LockAsync · AsyncLock · Nito.AsyncEx | 37.5934 ns | 4.529 | 320 B |
| SpinWait · SpinOnce · System | 41.3498 ns | 4.981 | - |
| SpinLock · SpinLock · System | 44.9287 ns | 5.412 | - |
| LockAsync · AsyncLock · NeoSmart | 55.3936 ns | 6.673 | 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.944 ns | 1.00 | - |
| Multiple · AsyncLock · Pooled (Task) | 0 | None | 10.279 ns | 1.03 | - |
| Multiple · AsyncLock · ProtoPromise | 0 | None | 11.144 ns | 1.12 | - |
| Multiple · SemaphoreSlim · System | 0 | None | 17.640 ns | 1.77 | - |
| Multiple · AsyncSemaphore · VS.Threading | 0 | None | 18.594 ns | 1.87 | - |
| Multiple · AsyncLock · RefImpl | 0 | None | 19.394 ns | 1.95 | - |
| Multiple · AsyncLock · NonKeyed | 0 | None | 20.600 ns | 2.07 | - |
| Multiple · AsyncLock · Nito | 0 | None | 39.078 ns | 3.93 | 320 B |
| Multiple · AsyncLock · NeoSmart | 0 | None | 58.457 ns | 5.88 | 208 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 0 | NotCancelled | 10.323 ns | 1.00 | - |
| Multiple · AsyncLock · Pooled (Task) | 0 | NotCancelled | 10.642 ns | 1.03 | - |
| Multiple · AsyncLock · ProtoPromise | 0 | NotCancelled | 11.886 ns | 1.15 | - |
| Multiple · SemaphoreSlim · System | 0 | NotCancelled | 17.517 ns | 1.70 | - |
| Multiple · AsyncSemaphore · VS.Threading | 0 | NotCancelled | 19.447 ns | 1.88 | - |
| Multiple · AsyncLock · NonKeyed | 0 | NotCancelled | 21.326 ns | 2.07 | - |
| Multiple · AsyncLock · Nito | 0 | NotCancelled | 38.806 ns | 3.76 | 320 B |
| Multiple · AsyncLock · NeoSmart | 0 | NotCancelled | 56.755 ns | 5.50 | 208 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 1 | None | 28.165 ns | 1.00 | - |
| Multiple · AsyncLock · ProtoPromise | 1 | None | 36.486 ns | 1.30 | - |
| Multiple · SemaphoreSlim · System | 1 | None | 42.075 ns | 1.49 | 88 B |
| Multiple · AsyncSemaphore · VS.Threading | 1 | None | 64.412 ns | 2.29 | 168 B |
| Multiple · AsyncLock · RefImpl | 1 | None | 76.101 ns | 2.70 | 216 B |
| Multiple · AsyncLock · Nito | 1 | None | 91.453 ns | 3.25 | 728 B |
| Multiple · AsyncLock · NeoSmart | 1 | None | 116.605 ns | 4.14 | 416 B |
| Multiple · AsyncLock · Pooled (Task) | 1 | None | 480.060 ns | 17.05 | 271 B |
| Multiple · AsyncLock · NonKeyed | 1 | None | 543.285 ns | 19.29 | 352 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 1 | NotCancelled | 46.467 ns | 1.00 | - |
| Multiple · AsyncLock · ProtoPromise | 1 | NotCancelled | 78.408 ns | 1.69 | - |
| Multiple · AsyncSemaphore · VS.Threading | 1 | NotCancelled | 79.121 ns | 1.70 | 168 B |
| Multiple · AsyncLock · NeoSmart | 1 | NotCancelled | 119.281 ns | 2.57 | 416 B |
| Multiple · AsyncLock · Nito | 1 | NotCancelled | 394.059 ns | 8.48 | 968 B |
| Multiple · AsyncLock · Pooled (Task) | 1 | NotCancelled | 516.424 ns | 11.12 | 272 B |
| Multiple · SemaphoreSlim · System | 1 | NotCancelled | 565.700 ns | 12.18 | 504 B |
| Multiple · AsyncLock · NonKeyed | 1 | NotCancelled | 660.724 ns | 14.22 | 640 B |
| Multiple · AsyncLock · ProtoPromise | 10 | None | 261.829 ns | 0.84 | - |
| Multiple · SemaphoreSlim · System | 10 | None | 270.938 ns | 0.87 | 880 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 10 | None | 309.915 ns | 1.00 | - |
| Multiple · AsyncSemaphore · VS.Threading | 10 | None | 511.846 ns | 1.65 | 1680 B |
| Multiple · AsyncLock · Nito | 10 | None | 542.904 ns | 1.75 | 4400 B |
| Multiple · AsyncLock · RefImpl | 10 | None | 619.827 ns | 2.00 | 2160 B |
| Multiple · AsyncLock · NeoSmart | 10 | None | 634.247 ns | 2.05 | 2288 B |
| Multiple · AsyncLock · Pooled (Task) | 10 | None | 3,191.047 ns | 10.30 | 1352 B |
| Multiple · AsyncLock · NonKeyed | 10 | None | 3,509.004 ns | 11.32 | 2296 B |
| Multiple · AsyncLock · ProtoPromise | 10 | NotCancelled | 466.087 ns | 0.91 | - |
| Multiple · AsyncLock · Pooled (ValueTask) | 10 | NotCancelled | 515.058 ns | 1.00 | - |
| Multiple · AsyncLock · NeoSmart | 10 | NotCancelled | 628.808 ns | 1.22 | 2288 B |
| Multiple · AsyncSemaphore · VS.Threading | 10 | NotCancelled | 700.242 ns | 1.36 | 1680 B |
| Multiple · AsyncLock · Nito | 10 | NotCancelled | 3,204.949 ns | 6.22 | 6800 B |
| Multiple · AsyncLock · Pooled (Task) | 10 | NotCancelled | 3,474.221 ns | 6.75 | 1352 B |
| Multiple · SemaphoreSlim · System | 10 | NotCancelled | 4,349.064 ns | 8.45 | 3888 B |
| Multiple · AsyncLock · NonKeyed | 10 | NotCancelled | 4,972.770 ns | 9.66 | 5176 B |
| Multiple · AsyncLock · ProtoPromise | 100 | None | 2,582.434 ns | 0.81 | - |
| Multiple · SemaphoreSlim · System | 100 | None | 2,587.874 ns | 0.82 | 8800 B |
| Multiple · AsyncLock · Pooled (ValueTask) | 100 | None | 3,169.397 ns | 1.00 | - |
| Multiple · AsyncSemaphore · VS.Threading | 100 | None | 4,740.432 ns | 1.50 | 21120 B |
| Multiple · AsyncLock · Nito | 100 | None | 5,232.310 ns | 1.65 | 41120 B |
| Multiple · AsyncLock · RefImpl | 100 | None | 6,004.082 ns | 1.89 | 21600 B |
| Multiple · AsyncLock · NeoSmart | 100 | None | 6,037.141 ns | 1.91 | 21008 B |
| Multiple · AsyncLock · Pooled (Task) | 100 | None | 34,158.735 ns | 10.78 | 12215 B |
| Multiple · AsyncLock · NonKeyed | 100 | None | 35,626.170 ns | 11.24 | 21800 B |
| Multiple · AsyncLock · ProtoPromise | 100 | NotCancelled | 4,536.588 ns | 0.91 | - |
| Multiple · AsyncLock · Pooled (ValueTask) | 100 | NotCancelled | 4,987.139 ns | 1.00 | - |
| Multiple · AsyncLock · NeoSmart | 100 | NotCancelled | 5,916.785 ns | 1.19 | 21008 B |
| Multiple · AsyncSemaphore · VS.Threading | 100 | NotCancelled | 6,643.265 ns | 1.33 | 21120 B |
| Multiple · AsyncLock · Pooled (Task) | 100 | NotCancelled | 33,360.289 ns | 6.69 | 12216 B |
| Multiple · AsyncLock · Nito | 100 | NotCancelled | 33,478.485 ns | 6.71 | 65120 B |
| Multiple · SemaphoreSlim · System | 100 | NotCancelled | 43,340.455 ns | 8.69 | 37792 B |
| Multiple · AsyncLock · NonKeyed | 100 | NotCancelled | 51,865.969 ns | 10.40 | 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.5349 ns | 0.79 | - |
| Set · AsyncAutoReset · Pooled | 0.6812 ns | 1.00 | - |
| Set · AsyncAutoReset · RefImpl | 4.1678 ns | 6.12 | - |
| Set · AsyncAutoReset · Nito.AsyncEx | 4.2622 ns | 6.26 | - |
| Set · AutoResetEvent · System | 217.0665 ns | 318.79 | - |
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.294 ns | 0.84 | - |
| SetThenWait · AsyncAutoReset · Pooled (ValueTask) | 6.303 ns | 1.00 | - |
| SetThenWait · AsyncAutoReset · Pooled (AsTask) | 7.069 ns | 1.12 | - |
| SetThenWait · AsyncAutoReset · Nito.AsyncEx | 13.545 ns | 2.15 | - |
| SetThenWait · AsyncAutoReset · RefImpl | 14.940 ns | 2.37 | - |
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.11 ns | 0.92 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 1 | None | 23.12 ns | 0.92 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 1 | None | 23.21 ns | 0.92 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 1 | None | 25.04 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 1 | None | 25.11 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 1 | None | 27.91 ns | 1.11 | 96 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 1 | None | 33.73 ns | 1.34 | 160 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 1 | None | 35.44 ns | 1.41 | 80 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 1 | None | 447.19 ns | 17.81 | 231 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 1 | NotCancelled | 38.70 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 1 | NotCancelled | 38.71 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 1 | NotCancelled | 40.22 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 1 | NotCancelled | 40.32 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 1 | NotCancelled | 44.87 ns | 1.11 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 1 | NotCancelled | 58.77 ns | 1.46 | 80 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 1 | NotCancelled | 303.38 ns | 7.53 | 400 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 1 | NotCancelled | 492.10 ns | 12.21 | 232 B |
| WaitThenSet · AsyncAutoReset · RefImpl | 2 | None | 52.17 ns | 0.84 | 192 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 2 | None | 56.13 ns | 0.91 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 2 | None | 57.55 ns | 0.93 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 2 | None | 60.42 ns | 0.98 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 2 | None | 61.89 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 2 | None | 62.63 ns | 1.01 | 320 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 2 | None | 87.36 ns | 1.41 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 2 | None | 91.08 ns | 1.47 | 160 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 2 | None | 753.69 ns | 12.18 | 343 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 2 | NotCancelled | 88.06 ns | 0.91 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 2 | NotCancelled | 92.75 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 2 | NotCancelled | 94.59 ns | 0.98 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 2 | NotCancelled | 96.28 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 2 | NotCancelled | 96.54 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 2 | NotCancelled | 138.99 ns | 1.44 | 160 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 2 | NotCancelled | 583.75 ns | 6.05 | 800 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 2 | NotCancelled | 873.77 ns | 9.05 | 344 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 10 | None | 233.91 ns | 0.59 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 10 | None | 268.65 ns | 0.68 | 960 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 10 | None | 303.53 ns | 0.76 | 1600 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 10 | None | 312.26 ns | 0.79 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 10 | None | 317.88 ns | 0.80 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 10 | None | 342.02 ns | 0.86 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 10 | None | 397.56 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 10 | None | 514.99 ns | 1.30 | 800 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 10 | None | 2,049.85 ns | 5.16 | 1236 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 10 | NotCancelled | 437.31 ns | 0.85 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 10 | NotCancelled | 491.00 ns | 0.96 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 10 | NotCancelled | 496.15 ns | 0.97 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 10 | NotCancelled | 513.49 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 10 | NotCancelled | 515.71 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 10 | NotCancelled | 751.00 ns | 1.46 | 800 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 10 | NotCancelled | 2,627.47 ns | 5.12 | 4000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 10 | NotCancelled | 2,716.97 ns | 5.29 | 1238 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 100 | None | 2,202.94 ns | 0.66 | - |
| WaitThenSet · AsyncAutoReset · RefImpl | 100 | None | 2,686.45 ns | 0.81 | 9600 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 100 | None | 2,922.77 ns | 0.88 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 100 | None | 2,947.52 ns | 0.89 | - |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 100 | None | 3,133.24 ns | 0.94 | 16000 B |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 100 | None | 3,296.53 ns | 0.99 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 100 | None | 3,323.51 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 100 | None | 4,866.06 ns | 1.46 | 8000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 100 | None | 15,749.32 ns | 4.74 | 11320 B |
| WaitThenSet · AsyncAutoReset · ProtoPromise | 100 | NotCancelled | 4,322.77 ns | 0.85 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask) | 100 | NotCancelled | 4,821.08 ns | 0.95 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsValueTask SyncCont) | 100 | NotCancelled | 4,975.09 ns | 0.98 | - |
| WaitThenSet · AsyncAutoReset · Pooled (ValueTask) | 100 | NotCancelled | 5,076.02 ns | 1.00 | - |
| WaitThenSet · AsyncAutoReset · Pooled (SyncCont) | 100 | NotCancelled | 5,438.26 ns | 1.07 | - |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask SyncCont) | 100 | NotCancelled | 7,519.64 ns | 1.48 | 8000 B |
| WaitThenSet · AsyncAutoReset · Nito.AsyncEx | 100 | NotCancelled | 33,761.27 ns | 6.65 | 40000 B |
| WaitThenSet · AsyncAutoReset · Pooled (AsTask) | 100 | NotCancelled | 407,038.78 ns | 80.20 | 11326 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.424 ns | 0.70 | - |
| SetReset · AsyncManualReset · Pooled | 2.043 ns | 1.00 | - |
| SetReset · ManualResetEventSlim · System | 5.633 ns | 2.76 | - |
| SetReset · AsyncManualReset · RefImpl | 10.499 ns | 5.14 | 96 B |
| SetReset · AsyncManualReset · Nito.AsyncEx | 17.007 ns | 8.33 | 96 B |
| SetReset · ManualResetEvent · System | 426.864 ns | 208.98 | - |
SetThenWait
| Description | Mean | Ratio | Allocated |
|---|---|---|---|
| SetThenWait · AsyncManualReset · ProtoPromise | 6.130 ns | 0.71 | - |
| SetThenWait · AsyncManualReset · Pooled (ValueTask) | 8.679 ns | 1.00 | - |
| SetThenWait · AsyncManualReset · Pooled (AsTask) | 10.352 ns | 1.19 | - |
| SetThenWait · AsyncManualReset · RefImpl | 14.084 ns | 1.62 | 96 B |
| SetThenWait · AsyncManualReset · Nito.AsyncEx | 24.532 ns | 2.83 | 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.77 ns | 0.85 | 96 B |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 1 | None | 23.40 ns | 0.92 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 1 | None | 24.13 ns | 0.94 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 1 | None | 25.07 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 1 | None | 25.56 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · ProtoPromise | 1 | None | 26.57 ns | 1.04 | - |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 1 | None | 28.83 ns | 1.13 | 96 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 1 | None | 36.29 ns | 1.42 | 80 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 1 | None | 442.78 ns | 17.33 | 231 B |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 1 | NotCancelled | 38.68 ns | 0.94 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 1 | NotCancelled | 40.45 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 1 | NotCancelled | 40.63 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 1 | NotCancelled | 41.05 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · ProtoPromise | 1 | NotCancelled | 49.45 ns | 1.21 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 1 | NotCancelled | 60.87 ns | 1.48 | 80 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 1 | NotCancelled | 492.64 ns | 12.01 | 232 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 1 | NotCancelled | 650.27 ns | 15.85 | 808 B |
| WaitThenSet · AsyncManualReset · RefImpl | 2 | None | 26.16 ns | 0.46 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 2 | None | 39.16 ns | 0.69 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 2 | None | 46.20 ns | 0.81 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 2 | None | 51.22 ns | 0.90 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 2 | None | 53.31 ns | 0.93 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 2 | None | 54.87 ns | 0.96 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 2 | None | 57.20 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 2 | None | 82.64 ns | 1.45 | 160 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 2 | None | 714.79 ns | 12.50 | 343 B |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 2 | NotCancelled | 89.91 ns | 0.96 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 2 | NotCancelled | 91.04 ns | 0.97 | - |
| WaitThenSet · AsyncManualReset · ProtoPromise | 2 | NotCancelled | 91.89 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 2 | NotCancelled | 93.81 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 2 | NotCancelled | 95.38 ns | 1.02 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 2 | NotCancelled | 129.08 ns | 1.38 | 160 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 2 | NotCancelled | 803.51 ns | 8.57 | 344 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 2 | NotCancelled | 1,185.20 ns | 12.64 | 1488 B |
| WaitThenSet · AsyncManualReset · RefImpl | 10 | None | 64.93 ns | 0.20 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 10 | None | 110.41 ns | 0.34 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 10 | None | 214.27 ns | 0.66 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 10 | None | 298.82 ns | 0.92 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 10 | None | 300.43 ns | 0.93 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 10 | None | 324.51 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 10 | None | 325.91 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 10 | None | 453.70 ns | 1.40 | 800 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 10 | None | 1,971.93 ns | 6.08 | 1239 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 10 | NotCancelled | 454.68 ns | 0.88 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 10 | NotCancelled | 482.97 ns | 0.94 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 10 | NotCancelled | 497.87 ns | 0.97 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 10 | NotCancelled | 515.24 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 10 | NotCancelled | 531.65 ns | 1.03 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 10 | NotCancelled | 737.97 ns | 1.43 | 800 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 10 | NotCancelled | 3,031.89 ns | 5.88 | 1240 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 10 | NotCancelled | 4,436.31 ns | 8.61 | 6464 B |
| WaitThenSet · AsyncManualReset · RefImpl | 100 | None | 533.53 ns | 0.15 | 96 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 100 | None | 928.28 ns | 0.27 | 96 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 100 | None | 2,275.95 ns | 0.65 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 100 | None | 2,857.04 ns | 0.82 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 100 | None | 2,901.30 ns | 0.83 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 100 | None | 3,161.49 ns | 0.91 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 100 | None | 3,482.75 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 100 | None | 4,674.61 ns | 1.34 | 8000 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 100 | None | 15,704.57 ns | 4.51 | 11320 B |
| WaitThenSet · AsyncManualReset · ProtoPromise | 100 | NotCancelled | 4,415.36 ns | 0.88 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask SyncCont) | 100 | NotCancelled | 4,908.97 ns | 0.98 | - |
| WaitThenSet · AsyncManualReset · Pooled (SyncCont) | 100 | NotCancelled | 4,971.60 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsValueTask) | 100 | NotCancelled | 4,996.67 ns | 0.99 | - |
| WaitThenSet · AsyncManualReset · Pooled (ValueTask) | 100 | NotCancelled | 5,026.19 ns | 1.00 | - |
| WaitThenSet · AsyncManualReset · Pooled (AsTask SyncCont) | 100 | NotCancelled | 7,174.00 ns | 1.43 | 8000 B |
| WaitThenSet · AsyncManualReset · Nito.AsyncEx | 100 | NotCancelled | 115,582.83 ns | 23.00 | 61617 B |
| WaitThenSet · AsyncManualReset · Pooled (AsTask) | 100 | NotCancelled | 263,666.83 ns | 52.46 | 11327 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.506 ns | 0.71 | - |
| WaitRelease · AsyncSemaphore · Pooled | 9.162 ns | 1.00 | - |
| WaitRelease · AsyncSemaphore · Nito.AsyncEx | 14.745 ns | 1.61 | - |
| WaitRelease · SemaphoreSlim · System | 16.958 ns | 1.85 | - |
| WaitRelease · AsyncSemaphore · RefImpl | 18.116 ns | 1.98 | - |
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.661 ns | 0.83 | - |
| SignalAndWait · AsyncCountdownEv · ProtoPromise | 1 | 7.370 ns | 0.92 | - |
| SignalAndWait · AsyncCountdownEv · Pooled | 1 | 8.016 ns | 1.00 | - |
| SignalAndWait · AsyncCountdownEv · RefImpl | 1 | 15.803 ns | 1.97 | 96 B |
| WaitAndSignal · AsyncCountdownEv · ProtoPromise | 1 | 18.378 ns | 2.29 | - |
| WaitAndSignal · AsyncCountdownEv · Pooled | 1 | 43.926 ns | 5.48 | - |
| SignalAndWait · AsyncCountdownEv · ProtoPromise | 10 | 17.067 ns | 0.77 | - |
| SignalAndWait · CountdownEvent · System | 10 | 20.834 ns | 0.94 | - |
| SignalAndWait · AsyncCountdownEv · Pooled | 10 | 22.233 ns | 1.00 | - |
| WaitAndSignal · AsyncCountdownEv · ProtoPromise | 10 | 28.077 ns | 1.26 | - |
| SignalAndWait · AsyncCountdownEv · RefImpl | 10 | 28.195 ns | 1.27 | 96 B |
| WaitAndSignal · AsyncCountdownEv · Pooled | 10 | 57.694 ns | 2.60 | - |
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 | 10.85 ns | 1.00 | - |
| SignalAndWait · Barrier · System | 1 | 452.84 ns | 41.73 | 238 B |
| SignalAndWait · AsyncBarrier · RefImpl | 1 | 936.26 ns | 86.29 | 8347 B |
| SignalAndWait · AsyncBarrier · Pooled | 10 | 254.53 ns | 1.00 | - |
| SignalAndWait · AsyncBarrier · RefImpl | 10 | 1,667.41 ns | 6.55 | 10252 B |
| SignalAndWait · Barrier · System | 10 | 40,505.19 ns | 159.16 | 1446 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.905 ns | 0.66 | - |
| WriterLock · AsyncRWLock · Proto.Promises | 8.379 ns | 0.80 | - |
| WriterLock · AsyncRWLock · Pooled | 10.460 ns | 1.00 | - |
| WriterLock · AsyncRWLock · RefImpl | 18.797 ns | 1.80 | - |
| WriterLock · AsyncRWLock · Nito.AsyncEx | 54.581 ns | 5.22 | 496 B |
| WriterLock · AsyncRWLock · VS.Threading | 1,027.868 ns | 98.27 | 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 | 6.774 ns | 0.40 | - |
| ReaderLock · AsyncRWLock · Pooled | 0 | None | 17.075 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 0 | None | 18.400 ns | 1.08 | - |
| ReaderLock · AsyncRWLock · RefImpl | 0 | None | 18.917 ns | 1.11 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 0 | None | 40.428 ns | 2.37 | 320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 0 | None | 224.937 ns | 13.17 | 208 B |
| ReaderLock · AsyncRWLock · Pooled | 0 | NotCancelled | 16.943 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 18.193 ns | 1.07 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 0 | NotCancelled | 39.961 ns | 2.36 | 320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 224.973 ns | 13.28 | 208 B |
| ReaderLock · RWLockSlim · System | 1 | None | 12.461 ns | 0.36 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 1 | None | 28.467 ns | 0.82 | - |
| ReaderLock · AsyncRWLock · Pooled | 1 | None | 34.709 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · RefImpl | 1 | None | 34.977 ns | 1.01 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 1 | None | 84.985 ns | 2.45 | 640 B |
| ReaderLock · AsyncRWLock · VS.Threading | 1 | None | 518.755 ns | 14.95 | 416 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 28.772 ns | 0.84 | - |
| ReaderLock · AsyncRWLock · Pooled | 1 | NotCancelled | 34.054 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 1 | NotCancelled | 81.086 ns | 2.38 | 640 B |
| ReaderLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 518.476 ns | 15.23 | 416 B |
| ReaderLock · RWLockSlim · System | 10 | None | 61.761 ns | 0.31 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 10 | None | 142.806 ns | 0.72 | - |
| ReaderLock · AsyncRWLock · RefImpl | 10 | None | 144.870 ns | 0.74 | - |
| ReaderLock · AsyncRWLock · Pooled | 10 | None | 197.022 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 10 | None | 481.419 ns | 2.44 | 3520 B |
| ReaderLock · AsyncRWLock · VS.Threading | 10 | None | 3,703.521 ns | 18.80 | 2288 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 10 | NotCancelled | 147.119 ns | 0.74 | - |
| ReaderLock · AsyncRWLock · Pooled | 10 | NotCancelled | 197.696 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 10 | NotCancelled | 473.137 ns | 2.39 | 3520 B |
| ReaderLock · AsyncRWLock · VS.Threading | 10 | NotCancelled | 3,614.872 ns | 18.29 | 2288 B |
| ReaderLock · RWLockSlim · System | 100 | None | 559.414 ns | 0.33 | - |
| ReaderLock · AsyncRWLock · RefImpl | 100 | None | 1,220.891 ns | 0.72 | - |
| ReaderLock · AsyncRWLock · Proto.Promises | 100 | None | 1,236.741 ns | 0.73 | - |
| ReaderLock · AsyncRWLock · Pooled | 100 | None | 1,700.744 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 100 | None | 4,495.417 ns | 2.64 | 32320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 100 | None | 87,591.125 ns | 51.50 | 21008 B |
| ReaderLock · AsyncRWLock · Proto.Promises | 100 | NotCancelled | 1,256.133 ns | 0.74 | - |
| ReaderLock · AsyncRWLock · Pooled | 100 | NotCancelled | 1,698.319 ns | 1.00 | - |
| ReaderLock · AsyncRWLock · Nito.AsyncEx | 100 | NotCancelled | 4,403.054 ns | 2.59 | 32320 B |
| ReaderLock · AsyncRWLock · VS.Threading | 100 | NotCancelled | 86,857.592 ns | 51.14 | 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.855 ns | 0.43 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 0 | None | 15.959 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 0 | None | 20.142 ns | 1.26 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 0 | None | 1,096.171 ns | 68.69 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 0 | NotCancelled | 16.321 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 18.987 ns | 1.16 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 1,127.899 ns | 69.11 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 1 | None | 6.745 ns | 0.37 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 1 | None | 18.139 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 1 | None | 19.333 ns | 1.07 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 1 | None | 1,089.110 ns | 60.04 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 17.789 ns | 0.90 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 1 | NotCancelled | 19.749 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 1,138.793 ns | 57.77 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 2 | None | 6.822 ns | 0.37 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 2 | None | 17.518 ns | 0.96 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 2 | None | 18.268 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 2 | None | 1,082.163 ns | 59.24 | 616 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 2 | NotCancelled | 17.317 ns | 0.93 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 2 | NotCancelled | 18.599 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 2 | NotCancelled | 1,124.084 ns | 60.44 | 616 B |
| UpgradeableReaderLock · RWLockSlim · System | 5 | None | 24.061 ns | 0.35 | - |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 5 | None | 54.872 ns | 0.80 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 5 | None | 68.248 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 5 | None | 2,631.592 ns | 38.56 | 1240 B |
| UpgradeableReaderLock · AsyncRWLock · Proto.Promises | 5 | NotCancelled | 53.997 ns | 0.80 | - |
| UpgradeableReaderLock · AsyncRWLock · Pooled | 5 | NotCancelled | 67.902 ns | 1.00 | - |
| UpgradeableReaderLock · AsyncRWLock · VS.Threading | 5 | NotCancelled | 2,693.549 ns | 39.67 | 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.46 ns | 0.60 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 0 | None | 21.66 ns | 0.97 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 0 | None | 22.44 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 0 | None | 1,823.37 ns | 81.24 | 824 B |
| UpgradedWriterLock · AsyncRWLock · Pooled | 0 | NotCancelled | 22.76 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 0 | NotCancelled | 24.61 ns | 1.08 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 0 | NotCancelled | 1,910.35 ns | 83.95 | 824 B |
| UpgradedWriterLock · RWLockSlim · System | 1 | None | 20.15 ns | 0.41 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 1 | None | 43.06 ns | 0.87 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 1 | None | 49.25 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 1 | None | 2,325.23 ns | 47.21 | 1032 B |
| UpgradedWriterLock · AsyncRWLock · Pooled | 1 | NotCancelled | 60.90 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 1 | NotCancelled | 70.48 ns | 1.16 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 1 | NotCancelled | 2,406.80 ns | 39.52 | 1032 B |
| UpgradedWriterLock · RWLockSlim · System | 2 | None | 25.36 ns | 0.37 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 2 | None | 54.15 ns | 0.80 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 2 | None | 68.01 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 2 | None | 2,823.31 ns | 41.56 | 1240 B |
| UpgradedWriterLock · AsyncRWLock · Pooled | 2 | NotCancelled | 76.14 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 2 | NotCancelled | 86.37 ns | 1.13 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 2 | NotCancelled | 2,948.51 ns | 38.73 | 1240 B |
| UpgradedWriterLock · RWLockSlim · System | 5 | None | 41.38 ns | 0.33 | - |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 5 | None | 92.94 ns | 0.75 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 5 | None | 123.96 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 5 | None | 4,468.96 ns | 36.05 | 1864 B |
| UpgradedWriterLock · AsyncRWLock · Proto.Promises | 5 | NotCancelled | 116.85 ns | 0.90 | - |
| UpgradedWriterLock · AsyncRWLock · Pooled | 5 | NotCancelled | 130.50 ns | 1.00 | - |
| UpgradedWriterLock · AsyncRWLock · VS.Threading | 5 | NotCancelled | 4,578.74 ns | 35.09 | 1864 B |