-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathbenchmarks.json
More file actions
235 lines (235 loc) · 21.3 KB
/
Copy pathbenchmarks.json
File metadata and controls
235 lines (235 loc) · 21.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
{
"schema": 1,
"machine": "AMD Ryzen 7 7800X3D (8 cores / 16 threads, 4.2 GHz), 64 GB, Windows 11",
"runtime": ".NET 10, Release, Server GC, built-in serializer",
"sets": {
"headline": {
"title": "JSON-RPC.Net 1.2.3 and 2.0 on one machine",
"date": "2026-09-25",
"source": "README.md, Performance table; branch async-scratch-cache",
"harness": "benchmarks/Baseline (1.2.3 from NuGet), then TestServer_Console menu entry t, --sync 3 16 and --async 3 16, one session",
"workload": "the five benchmark requests; the string rows are the best batch size of the thread-pool loop, the byte rows a loop on 16 dedicated threads or 16 awaited workers",
"conditions": "idle box; one run per row",
"policy": "one run per row, all in one session; the day-to-day ranges are the sync, async, legacy and kestrel sets",
"groups": [
{ "heading": "1.2.3, the last 1.x release on NuGet", "rule": false, "series": ["headline-123-string"] },
{ "heading": "2.0, the same string API and the same loop", "rule": false, "series": ["headline-20-string"] },
{ "heading": "2.0, the byte entry points", "rule": false, "series": ["headline-20-sync", "headline-20-async"] }
],
"series": [
{ "id": "headline-123-string", "label": "Task<string> Process(string), thread pool", "note": "best batch size, 1,200; Json.NET on every request", "family": "scale", "low": 3080000, "high": 3080000, "status": "single", "published": "3.08 M" },
{ "id": "headline-20-string", "label": "Task<string> Process(string), thread pool", "note": "best batch size, 36,000; the 1.x overloads, kept for compatibility", "family": "ours", "low": 13300000, "high": 13300000, "status": "single", "published": "13.3 M" },
{ "id": "headline-20-sync", "label": "Process(bytes), 16 dedicated threads", "note": "the library alone; 0 B per numeric request", "family": "ours", "low": 31700000, "high": 31700000, "status": "single", "published": "31.7 M" },
{ "id": "headline-20-async", "label": "ProcessAsync(bytes), 16 awaited workers", "note": "ValueTask<T> methods, RpcContextFlow.None; 6 B per request", "family": "ours", "low": 32100000, "high": 32100000, "status": "single", "published": "32.1 M" }
]
},
"sync": {
"title": "JSON-RPC.Net alone, by worker threads",
"date": "2026-09-25",
"source": "README.md, Sync table; branch async-scratch-cache",
"harness": "TestServer_Console --sync 3, and --sync 2 <n> for single rows",
"workload": "the five benchmark requests through the byte-level JsonRpcProcessor.Process in a loop; no scheduler, no transport",
"conditions": "idle box; three 3 s runs of --sync 3 in one session",
"policy": "low and high over three runs; ns per request is the harness's reported figure for one of them",
"x": { "name": "worker threads", "values": [1, 2, 4, 8, 16] },
"series": [
{
"id": "ours-sync",
"label": "JSON-RPC.Net, byte entry point",
"family": "ours",
"points": [
{ "low": 4570000, "high": 5000000, "status": "range", "published": "4.6 M to 5.0 M", "ns": 214 },
{ "low": 8500000, "high": 9100000, "status": "range", "published": "8.5 M to 9.1 M", "ns": 235 },
{ "low": 16600000, "high": 17100000, "status": "range", "published": "16.6 M to 17.1 M", "ns": 234 },
{ "low": 22800000, "high": 27700000, "status": "range", "published": "22.8 M to 27.7 M", "ns": 292 },
{ "low": 31700000, "high": 36400000, "status": "range", "published": "31.7 M to 36.4 M", "ns": 439 }
]
}
]
},
"async16": {
"title": "ProcessAsync, 16 awaited workers, by registration",
"date": "2026-09-25",
"source": "README.md, Async table, 16-worker column; branch async-scratch-cache",
"harness": "TestServer_Console --async 3 16",
"workload": "the five benchmark requests (yieldsOnce: one request) through the byte-level JsonRpcProcessor.ProcessAsync from Task.Run workers that await each call",
"conditions": "idle box; one 3 s run per row",
"policy": "one run per row",
"groups": [
{ "heading": "methods that complete inline, RpcContextFlow.None", "rule": false, "series": ["async16-sync-none", "async16-task-none", "async16-valuetask-none"] },
{ "heading": "methods that complete inline, RpcContextFlow.Flow", "rule": false, "series": ["async16-task-flow", "async16-valuetask-flow"] },
{ "heading": "one real suspension per request (yieldsOnce)", "rule": true, "series": ["async16-yield-none", "async16-yield-flow"] }
],
"series": [
{ "id": "async16-sync-none", "label": "synchronous methods, None", "note": "6 B per request, the StringMe result string", "family": "ours", "low": 24600000, "high": 24600000, "status": "single", "published": "24.6 M" },
{ "id": "async16-task-none", "label": "Task<T>, None", "note": "67 B per request, the service's own Task.FromResult", "family": "ours", "low": 30000000, "high": 30000000, "status": "single", "published": "30.0 M" },
{ "id": "async16-valuetask-none", "label": "ValueTask<T>, None", "note": "6 B per request", "family": "ours", "low": 32100000, "high": 32100000, "status": "single", "published": "32.1 M" },
{ "id": "async16-task-flow", "label": "Task<T>, Flow", "note": "251 B per request", "family": "ours", "low": 22200000, "high": 22200000, "status": "single", "published": "22.2 M" },
{ "id": "async16-valuetask-flow", "label": "ValueTask<T>, Flow", "note": "190 B per request", "family": "ours", "low": 23400000, "high": 23400000, "status": "single", "published": "23.4 M" },
{ "id": "async16-yield-none", "label": "yieldsOnce, None", "note": "559 B per request", "family": "ours", "low": 8960000, "high": 8960000, "status": "single", "published": "8.96 M" },
{ "id": "async16-yield-flow", "label": "yieldsOnce, Flow", "note": "741 B per request", "family": "ours", "low": 6890000, "high": 6890000, "status": "single", "published": "6.89 M" }
]
},
"async1": {
"title": "ProcessAsync, one awaited worker, by registration",
"date": "2026-09-25",
"source": "README.md, Async table, 1-worker column; branch async-scratch-cache",
"harness": "TestServer_Console --async 3 1",
"workload": "as async16, one worker",
"conditions": "idle box; one 3 s run per row",
"policy": "one run per row",
"groups": [
{ "heading": "methods that complete inline, RpcContextFlow.None", "rule": false, "series": ["async1-sync-none", "async1-task-none", "async1-valuetask-none"] },
{ "heading": "methods that complete inline, RpcContextFlow.Flow", "rule": false, "series": ["async1-task-flow", "async1-valuetask-flow"] },
{ "heading": "one real suspension per request (yieldsOnce)", "rule": true, "series": ["async1-yield-none", "async1-yield-flow"] }
],
"series": [
{ "id": "async1-sync-none", "label": "synchronous methods, None", "family": "ours", "low": 3470000, "high": 3470000, "status": "single", "published": "3.47 M" },
{ "id": "async1-task-none", "label": "Task<T>, None", "family": "ours", "low": 3470000, "high": 3470000, "status": "single", "published": "3.47 M" },
{ "id": "async1-valuetask-none", "label": "ValueTask<T>, None", "family": "ours", "low": 3760000, "high": 3760000, "status": "single", "published": "3.76 M" },
{ "id": "async1-task-flow", "label": "Task<T>, Flow", "family": "ours", "low": 2940000, "high": 2940000, "status": "single", "published": "2.94 M" },
{ "id": "async1-valuetask-flow", "label": "ValueTask<T>, Flow", "family": "ours", "low": 3110000, "high": 3110000, "status": "single", "published": "3.11 M" },
{ "id": "async1-yield-none", "label": "yieldsOnce, None", "family": "ours", "low": 1140000, "high": 1140000, "status": "single", "published": "1.14 M" },
{ "id": "async1-yield-flow", "label": "yieldsOnce, Flow", "family": "ours", "low": 915000, "high": 915000, "status": "single", "published": "915 k" }
]
},
"legacy": {
"title": "Legacy string API through the thread pool, by batch size",
"date": "2026-09-25",
"source": "README.md, Legacy table; branch async-scratch-cache",
"harness": "TestServer_Console, menu entry t",
"workload": "the five benchmark requests through the 1.x Task<string> Process(string) overload, submitted in batches from every core through Parallel.For",
"conditions": "idle box; three runs of the t entry in one session",
"policy": "low and high over three runs",
"x": { "name": "batch size", "values": [50, 300, 6000, 36000, 252000, 2016000] },
"series": [
{
"id": "legacy-string",
"label": "JSON-RPC.Net, 1.x string overloads",
"family": "ours",
"points": [
{ "low": 1570000, "high": 2030000, "status": "range", "published": "1.6 M to 2.0 M" },
{ "low": 7990000, "high": 9190000, "status": "range", "published": "8.0 M to 9.2 M" },
{ "low": 12360000, "high": 13260000, "status": "range", "published": "12.4 M to 13.3 M" },
{ "low": 11570000, "high": 13290000, "status": "range", "published": "11.6 M to 13.3 M" },
{ "low": 7470000, "high": 7800000, "status": "range", "published": "7.5 M to 7.8 M" },
{ "low": 7490000, "high": 8030000, "status": "range", "published": "7.5 M to 8.0 M" }
]
}
]
},
"kestrel": {
"title": "JSON-RPC.Net by transport",
"date": "2026-09-25",
"source": "README.md, Kestrel table; branch async-scratch-cache",
"harness": "TestServer_Console --kestrel 3",
"workload": "AustinHarris.JsonRpc.AspNetCore on Kestrel, loopback, 16 clients on the server's 8 cores",
"conditions": "idle box; three 3 s runs of --kestrel 3 and two of --kestrel 3 async, one session",
"policy": "low and high over the runs: three for the default rows, two for the EnableAsyncMethods = true rows",
"groups": [
{ "heading": "EnableAsyncMethods = false (the default)", "rule": false, "series": ["ours-http-1", "ours-http-100", "ours-tcp"] },
{ "heading": "EnableAsyncMethods = true: every document through ProcessAsync", "rule": false, "series": ["ours-tcp-async-inline", "ours-tcp-async-yield"] },
{ "heading": "Reference: no transport", "rule": true, "series": ["ours-inproc-16"] }
],
"series": [
{ "id": "ours-http-1", "label": "HTTP, 1 request per POST", "note": "each client awaits one POST at a time; 88 to 130 µs per round trip", "family": "ours", "low": 123000, "high": 182000, "status": "range", "published": "123 k to 182 k" },
{ "id": "ours-http-100", "label": "HTTP, batch of 100 per POST", "note": "one POST outstanding per client, 100 RPCs in it", "family": "ours", "low": 12800000, "high": 14500000, "status": "range", "published": "12.8 M to 14.5 M" },
{ "id": "ours-tcp", "label": "TCP, 256 pipelined", "note": "JsonRpcConnectionHandler, 256 requests in flight per connection", "family": "ours", "low": 14300000, "high": 16500000, "status": "range", "published": "14.3 M to 16.5 M" },
{ "id": "ours-tcp-async-inline", "label": "TCP, 256 pipelined, methods that complete inline", "note": "--kestrel 3 async", "family": "ours", "low": 15000000, "high": 15400000, "status": "range", "published": "15.0 M to 15.4 M" },
{ "id": "ours-tcp-async-yield", "label": "TCP, 256 pipelined, methods that suspend once", "note": "five async Task<T> methods awaiting Task.Yield(); documents are sequential per connection", "family": "ours", "low": 1250000, "high": 1290000, "status": "range", "published": "1.25 M to 1.29 M" },
{ "id": "ours-inproc-16", "label": "in-process, 16 threads", "note": "the same requests through the byte entry point", "family": "scale", "low": 33800000, "high": 37100000, "status": "range", "published": "33.8 M to 37.1 M" }
]
},
"compare": {
"title": "JSON-RPC.Net vs StreamJsonRpc vs gRPC for .NET, 16 connections",
"date": "2026-09-23",
"source": "README.md, comparison table; commit 192a97f",
"harness": "TestServer_Console --compare 3",
"workload": "the same five calls on the same Kestrel; 16 connections or channels with 256 requests in flight each; every request carries \"jsonrpc\":\"2.0\"",
"conditions": "WSL VM shut down; two 3 s runs",
"policy": "observed low and high over two runs; a single figure is one value that both runs rounded to",
"versions": "StreamJsonRpc 2.25.29, gRPC for .NET 2.84.0",
"groups": [
{ "heading": "JSON-RPC.Net", "rule": false, "series": ["ours-tcp-jsonrpc"] },
{ "heading": "StreamJsonRpc 2.25", "rule": false, "series": ["sjr-newline-stj", "sjr-cl-stj", "sjr-cl-newtonsoft"] },
{ "heading": "gRPC for .NET 2.84: its .NET client shares the server's cores", "rule": false, "series": ["grpc-unary", "grpc-stream"] }
],
"series": [
{ "id": "ours-tcp-jsonrpc", "label": "over Kestrel TCP", "note": "raw documents", "family": "ours", "low": 13700000, "high": 14600000, "status": "range", "published": "13.7 M to 14.6 M" },
{ "id": "sjr-newline-stj", "label": "over Kestrel TCP", "note": "newline framing, System.Text.Json formatter", "family": "theirs", "low": 1380000, "high": 1440000, "status": "range", "published": "1.38 M to 1.44 M" },
{ "id": "sjr-cl-stj", "label": "over Kestrel TCP", "note": "Content-Length framing, System.Text.Json formatter", "family": "theirs", "low": 1410000, "high": 1450000, "status": "range", "published": "1.41 M to 1.45 M" },
{ "id": "sjr-cl-newtonsoft", "label": "over Kestrel TCP", "note": "Content-Length framing, Json.NET formatter (default)", "family": "theirs", "low": 625000, "high": 625000, "status": "single", "published": "625 k" },
{ "id": "grpc-unary", "label": "unary calls", "note": "HTTP/2 + protobuf, 16 channels × 256 in flight", "family": "grpc", "low": 192000, "high": 198000, "status": "range", "published": "192 k to 198 k" },
{ "id": "grpc-stream", "label": "bidirectional stream", "note": "one stream per channel, 256 in flight, batched writes", "family": "grpc", "low": 200000, "high": 209000, "status": "range", "published": "200 k to 209 k" }
]
},
"inprocess": {
"title": "In-process paths: different execution boundaries",
"date": "2026-09-23",
"source": "README.md, comparison table; commit 192a97f",
"harness": "TestServer_Console --compare 3",
"workload": "the same five calls with no network: a direct call, a Pipe pair, a typed proxy",
"conditions": "WSL VM shut down; two 3 s runs",
"policy": "observed low and high over two runs",
"groups": [
{ "heading": "", "rule": false, "series": ["ours-direct-1", "sjr-pipe", "sjr-proxy"] }
],
"series": [
{ "id": "ours-direct-1", "label": "JSON-RPC.Net, direct call, 1 thread", "note": "bytes in, bytes out, no transport", "family": "ours", "low": 2600000, "high": 3600000, "status": "range", "published": "2.6 M to 3.6 M" },
{ "id": "sjr-pipe", "label": "StreamJsonRpc, Pipe pair, 1 client", "note": "newline framing, System.Text.Json, 256 pipelined", "family": "theirs", "low": 117000, "high": 142000, "status": "range", "published": "117 k to 142 k" },
{ "id": "sjr-proxy", "label": "StreamJsonRpc, typed proxy", "note": "sequential await per call, 10 µs per round trip", "family": "theirs", "low": 96000, "high": 97000, "status": "range", "published": "96 k to 97 k" }
]
},
"sweep": {
"title": "Every library and transport, by client connections",
"source": "TestServer_Console --sweep 2 <file>; one file per run, matched by the pattern below",
"runs": "sweep*.json",
"workload": "the same five calls on the same Kestrel, clients on the server's 8 cores; each cell warmed for 2 s, then timed",
"conditions": "WSL VM running and other sessions active: a separate session from the tables, so its absolute figures are not comparable with them",
"policy": "observed low, high and median over the runs; each run contributes one observation per cell",
"x": { "name": "client connections (gRPC: channels)" },
"series": [
{ "id": "sweep-ours-tcp", "name": "JSON-RPC.Net, TCP", "label": "TCP", "family": "ours", "style": 0, "concurrency": "256 requests in flight per connection" },
{ "id": "sweep-ours-http-100", "name": "JSON-RPC.Net, HTTP, batch of 100 per POST", "label": "HTTP, batch of 100 per POST", "family": "ours", "style": 1, "concurrency": "one POST outstanding per connection, 100 RPCs in it" },
{ "id": "sweep-ours-http-1", "name": "JSON-RPC.Net, HTTP, 1 request per POST", "label": "HTTP, 1 request per POST", "family": "ours", "style": 2, "concurrency": "one POST outstanding per connection" },
{ "id": "sweep-sjr-newline-stj", "name": "StreamJsonRpc, TCP, newline + System.Text.Json", "label": "TCP, newline + System.Text.Json", "family": "theirs", "style": 0, "concurrency": "256 requests in flight per connection" },
{ "id": "sweep-sjr-cl-stj", "name": "StreamJsonRpc, TCP, Content-Length + System.Text.Json", "label": "TCP, Content-Length + System.Text.Json", "family": "theirs", "style": 1, "concurrency": "256 requests in flight per connection" },
{ "id": "sweep-sjr-cl-newtonsoft", "name": "StreamJsonRpc, TCP, Content-Length + Json.NET", "label": "TCP, Content-Length + Json.NET", "family": "theirs", "style": 2, "concurrency": "256 requests in flight per connection" },
{ "id": "sweep-grpc-unary", "name": "gRPC for .NET, unary", "label": "unary", "family": "grpc", "style": 0, "concurrency": "256 calls in flight per channel" },
{ "id": "sweep-grpc-stream", "name": "gRPC for .NET, bidirectional stream", "label": "bidirectional stream", "family": "grpc", "style": 1, "concurrency": "one stream per channel, 256 in flight, batched writes" }
]
},
"wasm": {
"title": "In the browser: interpreter vs AOT",
"date": "2026-09-23",
"source": "samples/WasmHost/README.md table; commit 41371ec",
"harness": "samples/WasmHost, Run benchmark in Chrome 152",
"workload": "add(1, 2) through each interop path, 20,000 RPCs per row",
"conditions": "8-core desktop; interpreter from dotnet run, AOT from dotnet publish -c Release with the wasm-tools workload",
"policy": "interpreter: one run; AOT: the better of two runs. The interpreter-to-AOT distance is a deployment difference, not a range",
"modes": [
{ "id": "interp", "label": "interpreter", "marker": "circle", "policy": "one run" },
{ "id": "aot", "label": "AOT", "marker": "diamond", "policy": "better of two runs" }
],
"groups": [
{ "heading": "plain Blazor interop, one method per operation", "rule": false, "series": ["wasm-plain-invoke", "wasm-plain-invoke-async", "wasm-plain-jsexport"] },
{ "heading": "JSON-RPC, one request document per call", "rule": false, "series": ["wasm-rpc-invoke", "wasm-rpc-invoke-async", "wasm-rpc-jsexport", "wasm-rpc-bytes"] },
{ "heading": "JSON-RPC, a batch of 100 per call", "rule": false, "series": ["wasm-rpc-invoke-100", "wasm-rpc-jsexport-100", "wasm-rpc-bytes-100"] },
{ "heading": "Reference: the server in a .NET loop, no interop", "rule": true, "series": ["wasm-loop"] }
],
"series": [
{ "id": "wasm-plain-invoke", "label": "invokeMethod Add", "family": "scale", "values": { "interp": 15700, "aot": 66600 }, "published": { "interp": "15,700", "aot": "66,600" } },
{ "id": "wasm-plain-invoke-async", "label": "invokeMethodAsync Add", "family": "scale", "values": { "interp": 14500, "aot": 59300 }, "published": { "interp": "14,500", "aot": "59,300" } },
{ "id": "wasm-plain-jsexport", "label": "[JSExport] AddExported, typed arguments", "family": "scale", "values": { "interp": 2860000, "aot": 3230000 }, "published": { "interp": "2,860,000", "aot": "3,230,000" } },
{ "id": "wasm-rpc-invoke", "label": "invokeMethod Process", "family": "ours", "values": { "interp": 6000, "aot": 33500 }, "published": { "interp": "6,000", "aot": "33,500" } },
{ "id": "wasm-rpc-invoke-async", "label": "invokeMethodAsync Process", "family": "ours", "values": { "interp": 5700, "aot": 36000 }, "published": { "interp": "5,700", "aot": "36,000" } },
{ "id": "wasm-rpc-jsexport", "label": "[JSExport] ProcessExported, strings", "family": "ours", "values": { "interp": 17100, "aot": 151600 }, "published": { "interp": "17,100", "aot": "151,600" } },
{ "id": "wasm-rpc-bytes", "label": "[JSExport] ProcessBytes, UTF-8 buffers", "family": "ours", "values": { "interp": 19000, "aot": 142000 }, "published": { "interp": "19,000", "aot": "142,000" } },
{ "id": "wasm-rpc-invoke-100", "label": "invokeMethod Process", "family": "ours", "values": { "interp": 13000, "aot": 136000 }, "published": { "interp": "13,000", "aot": "136,000" } },
{ "id": "wasm-rpc-jsexport-100", "label": "[JSExport] ProcessExported, strings", "family": "ours", "values": { "interp": 25000, "aot": 185500 }, "published": { "interp": "25,000", "aot": "185,500" } },
{ "id": "wasm-rpc-bytes-100", "label": "[JSExport] ProcessBytes, UTF-8 buffers", "family": "ours", "values": { "interp": 27000, "aot": 210500 }, "published": { "interp": "27,000", "aot": "210,500" } },
{ "id": "wasm-loop", "label": "ProcessMany, the string request N times", "family": "scale", "values": { "interp": 17600, "aot": 169800 }, "published": { "interp": "17,600", "aot": "169,800" } }
]
}
}
}