.NET Async Thread Pool Performance

View on GitHub

System Information & Benchmark Environment
Date: 3/17/2025 1:18:04 PM
OS: macOS 15.3.2
CPU: Apple M2 Max
CPU Cores: 12
Memory: 32.00 GB Total
Free Memory: 0.31 GB
.NET Version: .NET 8.0.1
.NET Runtime: 8.0.1
.NET Architecture: Arm64
.NET OS: Darwin 24.3.0 Darwin Kernel Version 24.3.0: Thu Jan 2 20:24:23 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6020
Node.js: v18.16.1
Load Tester: oha 1.5.0

This dashboard visualizes the performance impact of different thread pool configurations in .NET 8.0. The tests measure how various settings affect request throughput and CPU efficiency.

Key Finding: 7.0x Improvement in Efficiency!

By limiting worker threads to 1 and disabling semaphore spinning, we achieved 7.0x more requests per CPU core compared to the default configuration.

Best Configuration
1 .NET Threads - oha 2
1 .NET worker threads, no semaphore spin, oha 2 threads
Maximum Throughput
130,484 req/sec
Highest raw request throughput
Best Efficiency
84,447 req/sec/core
Highest throughput per CPU core used

Configuration Details

Configuration Description Throughput (req/sec) CPU Usage (%) * Min-Max CPU (%) Efficiency (req/sec/core)
1 .NET Threads - oha 2 1 .NET worker threads, no semaphore spin, oha 2 threads 112,337 133.0% 122.1-145.2 84,447 🔥
1 .NET Thread - oha 1 1 .NET worker threads, no semaphore spin, oha 1 thread 108,396 130.9% 124.6-154.8 82,795
2 .NET Threads - oha 2 2 .NET worker threads, no semaphore spin, oha 2 threads 130,484 240.1% 158.8-256.4 54,356
No Spin Default .NET worker threads, no semaphore spin, oha 1 thread 113,565 397.4% 375.7-490.1 28,575
Spin 10 Default .NET worker threads, semaphore spin 10, oha 1 thread 122,930 637.1% 478.9-789.1 19,295
Base Case Default .NET worker threads, default semaphore spin, oha 1 thread 112,629 933.6% 833.9-970.5 12,064
* CPU Usage shows the average usage across all samples. (est) indicates estimated values where measurement wasn't possible.

Problem and Solution

The core issue is that .NET's ThreadPool and async/await mechanics cause excessive thread context switching and CPU usage when handling HTTP requests. This is particularly problematic in high-throughput services.

Key observations: