When multiple requests use the same connection pool (same handler + same destination), they will hit a bunch of contention when acquiring and releasing the connection from the pool.
A quick example comparing the HttpClient benchmark using 1 vs 8 handlers, both with 256 worker tasks, shows a ~13% difference.
crank --config https://raw.githubusercontent.com/aspnet/Benchmarks/main/scenarios/httpclient.benchmarks.yml --scenario httpclient-kestrel-get --profile aspnet-citrine-lin --variable useHttpMessageInvoker=true --variable concurrencyPerHttpClient=256 --variable numberOfHttpClients=1 --json 1x256.json
crank --config https://raw.githubusercontent.com/aspnet/Benchmarks/main/scenarios/httpclient.benchmarks.yml --scenario httpclient-kestrel-get --profile aspnet-citrine-lin --variable useHttpMessageInvoker=true --variable concurrencyPerHttpClient=32 --variable numberOfHttpClients=8 --json 8x32.json
crank compare .\1x256.json .\8x32.json
| client |
1x256 |
8x32 |
|
| CPU Usage (%) |
75 |
95 |
+26,67% |
| Requests |
8.653.509 |
9.814.549 |
+13,42% |
| Mean RPS |
576.751 |
654.417 |
+13,47% |
I saw similar numbers (a ~15% E2E difference) in my LLRP experiment when switching to using multiple handlers.
Approximate numbers from a benchmark doing minimal work outside of spamming the connection pool:
| Threads |
RPS |
RPS/thread |
| 1 |
620k |
620k |
| 2 |
1120k |
560k |
| 3 |
1500k |
500k |
| 4 |
1720k |
430k |
| 5 |
1370k |
274k |
| 6 |
1170k |
195k |
| 7 |
1080k |
154k |
On my machine (8 core CPU) the Threads=6 scenario is spending 57 % of CPU on getting the lock in GetHttp11ConnectionAsync and ReturnHttp11Connection.

We should look into reducing the contention here if possible.
When multiple requests use the same connection pool (same handler + same destination), they will hit a bunch of contention when acquiring and releasing the connection from the pool.
A quick example comparing the HttpClient benchmark using 1 vs 8 handlers, both with 256 worker tasks, shows a ~13% difference.
I saw similar numbers (a ~15% E2E difference) in my LLRP experiment when switching to using multiple handlers.
Approximate numbers from a benchmark doing minimal work outside of spamming the connection pool:
On my machine (8 core CPU) the Threads=6 scenario is spending 57 % of CPU on getting the lock in

GetHttp11ConnectionAsyncandReturnHttp11Connection.We should look into reducing the contention here if possible.