|
| 1 | +# TurboHttp vs HttpClient — Localhost Kestrel (Loopback) |
| 2 | + |
| 3 | +| | | |
| 4 | +|---|---| |
| 5 | +| **Report date** | 2026-05-05 10:28 UTC | |
| 6 | +| **Server** | localhost Kestrel (127.0.0.1, dynamic port) | |
| 7 | +| **Protocol** | HTTP cleartext — HTTP/1.1 and HTTP/2 (h2c prior knowledge) | |
| 8 | +| **Light endpoint** | `GET /benchmark/simple` (~3 B text/plain) | |
| 9 | +| **Heavy endpoint** | `POST /benchmark/payload` (10 KB request body) | |
| 10 | + |
| 11 | +> **Legend:** |
| 12 | +> - ✓ faster than HttpClient by >5% |
| 13 | +> - – within ±5% of HttpClient |
| 14 | +> - ✗ slower than HttpClient by >5% |
| 15 | +> - **Δ%** is relative to the HttpClient baseline (positive = faster/cheaper) |
| 16 | +
|
| 17 | +# HTTP/1.1 |
| 18 | + |
| 19 | +## Single Request — Throughput (Req/sec — higher is better) |
| 20 | + |
| 21 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 22 | +|---|---:|---:|---:|---:|---:| |
| 23 | +| ConcurrentRequests_Light / CL=1 | 23,356 | 14,432 | -38.2% | 12,111 | -48.1% | |
| 24 | +| ConcurrentRequests_Heavy / CL=1 | 16,940 | 13,163 | -22.3% | 10,845 | -36.0% | |
| 25 | + |
| 26 | +## Single Request — Latency (ns — lower is better) |
| 27 | + |
| 28 | +### p50 (Median) |
| 29 | + |
| 30 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 31 | +|---|---:|---:|---:|---:|---:| |
| 32 | +| ConcurrentRequests_Light / CL=1 | 42,322 ns | 68,625 ns | -62.1% | 75,980 ns | -79.5% | |
| 33 | +| ConcurrentRequests_Heavy / CL=1 | 57,455 ns | 75,834 ns | -32.0% | 93,086 ns | -62.0% | |
| 34 | + |
| 35 | +### p95 |
| 36 | + |
| 37 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 38 | +|---|---:|---:|---:|---:|---:| |
| 39 | +| ConcurrentRequests_Light / CL=1 | 44,925 ns | 77,564 ns | -72.7% | 107,494 ns | -139.3% | |
| 40 | +| ConcurrentRequests_Heavy / CL=1 | 64,406 ns | 77,171 ns | -19.8% | 100,411 ns | -55.9% | |
| 41 | + |
| 42 | +### p99 |
| 43 | + |
| 44 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 45 | +|---|---:|---:|---:|---:|---:| |
| 46 | +| ConcurrentRequests_Light / CL=1 | 45,356 ns | 78,078 ns | -72.1% | 110,010 ns | -142.5% | |
| 47 | +| ConcurrentRequests_Heavy / CL=1 | 65,776 ns | 77,440 ns | -17.7% | 102,475 ns | -55.8% | |
| 48 | + |
| 49 | +## Single Request — Memory (Allocated bytes/op — lower is better) |
| 50 | + |
| 51 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 52 | +|---|---:|---:|---:|---:|---:| |
| 53 | +| ConcurrentRequests_Light / CL=1 | 2,704 B | 5,237 B | -93.7% | 5,173 B | -91.3% | |
| 54 | +| ConcurrentRequests_Heavy / CL=1 | 41,618 B | 44,224 B | -6.3% | 44,000 B | -5.7% | |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## Concurrent Benchmarks |
| 59 | + |
| 60 | +> N requests are fired simultaneously (SendAsync: `Task.WhenAll`; Streaming: channel write-all, drain-all). |
| 61 | +> **Throughput** = N / Mean (aggregate req/sec across all parallel slots). |
| 62 | +> **Latency** = elapsed wall-time until all N complete (lower is better). |
| 63 | +
|
| 64 | +### Concurrent Throughput (Req/sec — higher is better) |
| 65 | + |
| 66 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 67 | +|---|---:|---:|---:|---:|---:| |
| 68 | +| ConcurrentRequests_Light / CL=512 | 77,858 | 73,642 | -5.4% | 242,330 | +211.2% | |
| 69 | +| ConcurrentRequests_Heavy / CL=512 | 39,983 | 70,943 | +77.4% | 67,455 | +68.7% | |
| 70 | +| ConcurrentRequests_Light / CL=4096 | 79,490 | 117,319 | +47.6% | 300,845 | +278.5% | |
| 71 | +| ConcurrentRequests_Heavy / CL=4096 | 43,055 | 84,915 | +97.2% | 110,153 | +155.8% | |
| 72 | + |
| 73 | +### Concurrent Latency (ns — lower is better) |
| 74 | + |
| 75 | +#### p50 (Median) |
| 76 | + |
| 77 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 78 | +|---|---:|---:|---:|---:|---:| |
| 79 | +| ConcurrentRequests_Light / CL=512 | 6,588,207 ns | 5,423,530 ns | +17.7% | 1,996,716 ns | +69.7% | |
| 80 | +| ConcurrentRequests_Heavy / CL=512 | 12,955,241 ns | 6,837,216 ns | +47.2% | 7,692,003 ns | +40.6% | |
| 81 | +| ConcurrentRequests_Light / CL=4096 | 49,478,700 ns | 33,977,661 ns | +31.3% | 14,228,775 ns | +71.2% | |
| 82 | +| ConcurrentRequests_Heavy / CL=4096 | 94,241,425 ns | 46,708,275 ns | +50.4% | 37,826,442 ns | +59.9% | |
| 83 | + |
| 84 | +#### p95 |
| 85 | + |
| 86 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 87 | +|---|---:|---:|---:|---:|---:| |
| 88 | +| ConcurrentRequests_Light / CL=512 | 7,465,021 ns | 10,657,414 ns | -42.8% | 2,901,573 ns | +61.1% | |
| 89 | +| ConcurrentRequests_Heavy / CL=512 | 14,437,024 ns | 8,726,812 ns | +39.6% | 9,208,768 ns | +36.2% | |
| 90 | +| ConcurrentRequests_Light / CL=4096 | 57,498,041 ns | 42,218,934 ns | +26.6% | 17,150,301 ns | +70.2% | |
| 91 | +| ConcurrentRequests_Heavy / CL=4096 | 98,974,532 ns | 55,854,108 ns | +43.6% | 39,071,458 ns | +60.5% | |
| 92 | + |
| 93 | +#### p99 |
| 94 | + |
| 95 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 96 | +|---|---:|---:|---:|---:|---:| |
| 97 | +| ConcurrentRequests_Light / CL=512 | 7,625,899 ns | 10,830,154 ns | -42.0% | 2,970,250 ns | +61.1% | |
| 98 | +| ConcurrentRequests_Heavy / CL=512 | 14,647,485 ns | 8,929,927 ns | +39.0% | 9,235,972 ns | +36.9% | |
| 99 | +| ConcurrentRequests_Light / CL=4096 | 58,533,905 ns | 42,442,978 ns | +27.5% | 17,457,507 ns | +70.2% | |
| 100 | +| ConcurrentRequests_Heavy / CL=4096 | 99,633,666 ns | 55,940,292 ns | +43.9% | 39,433,762 ns | +60.4% | |
| 101 | + |
| 102 | +### Concurrent Memory (Allocated bytes/op — lower is better) |
| 103 | + |
| 104 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 105 | +|---|---:|---:|---:|---:|---:| |
| 106 | +| ConcurrentRequests_Light / CL=512 | 1,351,445 B | 2,513,379 B | -86.0% | 1,642,918 B | -21.6% | |
| 107 | +| ConcurrentRequests_Heavy / CL=512 | 21,481,607 B | 22,144,062 B | -3.1% | 20,448,074 B | +4.8% | |
| 108 | +| ConcurrentRequests_Light / CL=4096 | 10,811,462 B | 19,821,198 B | -83.3% | 13,078,302 B | -21.0% | |
| 109 | +| ConcurrentRequests_Heavy / CL=4096 | 171,843,902 B | 175,716,993 B | -2.3% | 210,727,631 B | -22.6% | |
| 110 | + |
| 111 | +# HTTP/2.0 |
| 112 | + |
| 113 | +## Single Request — Throughput (Req/sec — higher is better) |
| 114 | + |
| 115 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 116 | +|---|---:|---:|---:|---:|---:| |
| 117 | +| ConcurrentRequests_Light / CL=1 | 18,548 | 13,925 | -24.9% | 12,243 | -34.0% | |
| 118 | +| ConcurrentRequests_Heavy / CL=1 | 15,914 | 9,803 | -38.4% | 9,255 | -41.8% | |
| 119 | + |
| 120 | +## Single Request — Latency (ns — lower is better) |
| 121 | + |
| 122 | +### p50 (Median) |
| 123 | + |
| 124 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 125 | +|---|---:|---:|---:|---:|---:| |
| 126 | +| ConcurrentRequests_Light / CL=1 | 44,850 ns | 69,981 ns | -56.0% | 81,060 ns | -80.7% | |
| 127 | +| ConcurrentRequests_Heavy / CL=1 | 61,841 ns | 91,502 ns | -48.0% | 104,823 ns | -69.5% | |
| 128 | + |
| 129 | +### p95 |
| 130 | + |
| 131 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 132 | +|---|---:|---:|---:|---:|---:| |
| 133 | +| ConcurrentRequests_Light / CL=1 | 80,171 ns | 78,889 ns | +1.6% | 85,076 ns | -6.1% | |
| 134 | +| ConcurrentRequests_Heavy / CL=1 | 66,651 ns | 133,972 ns | -101.0% | 120,776 ns | -81.2% | |
| 135 | + |
| 136 | +### p99 |
| 137 | + |
| 138 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 139 | +|---|---:|---:|---:|---:|---:| |
| 140 | +| ConcurrentRequests_Light / CL=1 | 85,198 ns | 80,518 ns | +5.5% | 86,450 ns | -1.5% | |
| 141 | +| ConcurrentRequests_Heavy / CL=1 | 67,185 ns | 140,506 ns | -109.1% | 123,006 ns | -83.1% | |
| 142 | + |
| 143 | +## Single Request — Memory (Allocated bytes/op — lower is better) |
| 144 | + |
| 145 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 146 | +|---|---:|---:|---:|---:|---:| |
| 147 | +| ConcurrentRequests_Light / CL=1 | 3,385 B | 5,510 B | -62.8% | 5,294 B | -56.4% | |
| 148 | +| ConcurrentRequests_Heavy / CL=1 | 43,950 B | 44,807 B | -1.9% | 44,653 B | -1.6% | |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Concurrent Benchmarks |
| 153 | + |
| 154 | +> N requests are fired simultaneously (SendAsync: `Task.WhenAll`; Streaming: channel write-all, drain-all). |
| 155 | +> **Throughput** = N / Mean (aggregate req/sec across all parallel slots). |
| 156 | +> **Latency** = elapsed wall-time until all N complete (lower is better). |
| 157 | +
|
| 158 | +### Concurrent Throughput (Req/sec — higher is better) |
| 159 | + |
| 160 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 161 | +|---|---:|---:|---:|---:|---:| |
| 162 | +| ConcurrentRequests_Light / CL=512 | 130,294 | 201,153 | +54.4% | 266,409 | +104.5% | |
| 163 | +| ConcurrentRequests_Heavy / CL=512 | 48,801 | 56,287 | +15.3% | 56,143 | +15.0% | |
| 164 | +| ConcurrentRequests_Light / CL=4096 | 379,976 | 214,172 | -43.6% | 315,954 | -16.8% | |
| 165 | +| ConcurrentRequests_Heavy / CL=4096 | 113,657 | 80,337 | -29.3% | 156,437 | +37.6% | |
| 166 | + |
| 167 | +### Concurrent Latency (ns — lower is better) |
| 168 | + |
| 169 | +#### p50 (Median) |
| 170 | + |
| 171 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 172 | +|---|---:|---:|---:|---:|---:| |
| 173 | +| ConcurrentRequests_Light / CL=512 | 4,023,312 ns | 2,287,904 ns | +43.1% | 1,937,218 ns | +51.9% | |
| 174 | +| ConcurrentRequests_Heavy / CL=512 | 10,052,945 ns | 9,474,868 ns | +5.8% | 9,177,161 ns | +8.7% | |
| 175 | +| ConcurrentRequests_Light / CL=4096 | 9,265,517 ns | 17,794,035 ns | -92.0% | 12,577,429 ns | -35.7% | |
| 176 | +| ConcurrentRequests_Heavy / CL=4096 | 36,229,975 ns | 50,741,381 ns | -40.1% | 25,815,110 ns | +28.7% | |
| 177 | + |
| 178 | +#### p95 |
| 179 | + |
| 180 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 181 | +|---|---:|---:|---:|---:|---:| |
| 182 | +| ConcurrentRequests_Light / CL=512 | 4,459,141 ns | 3,547,133 ns | +20.5% | 2,299,195 ns | +48.4% | |
| 183 | +| ConcurrentRequests_Heavy / CL=512 | 15,131,136 ns | 10,173,815 ns | +32.8% | 10,507,704 ns | +30.6% | |
| 184 | +| ConcurrentRequests_Light / CL=4096 | 15,556,228 ns | 24,229,471 ns | -55.8% | 15,592,222 ns | -0.2% | |
| 185 | +| ConcurrentRequests_Heavy / CL=4096 | 39,385,431 ns | 56,815,354 ns | -44.3% | 33,660,218 ns | +14.5% | |
| 186 | + |
| 187 | +#### p99 |
| 188 | + |
| 189 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 190 | +|---|---:|---:|---:|---:|---:| |
| 191 | +| ConcurrentRequests_Light / CL=512 | 4,468,015 ns | 3,574,316 ns | +20.0% | 2,329,021 ns | +47.9% | |
| 192 | +| ConcurrentRequests_Heavy / CL=512 | 15,288,171 ns | 10,264,576 ns | +32.9% | 10,852,481 ns | +29.0% | |
| 193 | +| ConcurrentRequests_Light / CL=4096 | 15,880,833 ns | 24,587,174 ns | -54.8% | 15,899,704 ns | -0.1% | |
| 194 | +| ConcurrentRequests_Heavy / CL=4096 | 40,633,526 ns | 57,059,601 ns | -40.4% | 34,142,172 ns | +16.0% | |
| 195 | + |
| 196 | +### Concurrent Memory (Allocated bytes/op — lower is better) |
| 197 | + |
| 198 | +| Scenario | HttpClient | SendAsync | Δ% | Streaming | Δ% | |
| 199 | +|---|---:|---:|---:|---:|---:| |
| 200 | +| ConcurrentRequests_Light / CL=512 | 1,707,043 B | 2,387,265 B | -39.8% | 1,873,426 B | -9.7% | |
| 201 | +| ConcurrentRequests_Heavy / CL=512 | 24,447,072 B | 17,730,029 B | +27.5% | 17,817,293 B | +27.1% | |
| 202 | +| ConcurrentRequests_Light / CL=4096 | 13,620,632 B | 18,714,322 B | -37.4% | 15,534,137 B | -14.0% | |
| 203 | +| ConcurrentRequests_Heavy / CL=4096 | 201,487,630 B | 98,045,236 B | +51.3% | 128,515,193 B | +36.2% | |
| 204 | + |
| 205 | +## Notes |
| 206 | + |
| 207 | +- All requests target a localhost Kestrel server over loopback (127.0.0.1). |
| 208 | +- No TLS overhead — HTTP cleartext for both HTTP/1.1 and HTTP/2 (h2c). |
| 209 | +- Light: `GET /benchmark/simple` returns `OK\n` (~3 B). Heavy: `POST /benchmark/payload` with 10 KB body. |
| 210 | +- HTTP/2 uses h2c prior knowledge on a dedicated listener port. |
| 211 | +- Loopback eliminates network jitter — results reflect pure client+server overhead. |
| 212 | +- Memory figures reflect managed allocations only; native/pooled buffers are not included. |
| 213 | +- **Streaming** uses the channel API (`Requests` writer / `Responses` reader). |
| 214 | +- **SendAsync** uses `Task.WhenAll` fan-out; each concurrent slot gets its own `Task<HttpResponseMessage>`. |
| 215 | + |
0 commit comments