Repository navigation
Expand file tree
/
Copy pathexplorer.html
More file actions
361 lines (347 loc) · 59 KB
/
Copy pathexplorer.html
File metadata and controls
361 lines (347 loc) · 59 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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JSON-RPC.NET benchmark explorer</title>
<meta name="description" content="The benchmark data behind the JSON-RPC.NET README: toggle series, inspect exact values and ranges, switch axes, download the data.">
<meta name="data-revision" content="697798ae199d">
<style>
:root { --bg: #ffffff; --ink: #1f2328; --muted: #57606a; --grid: #e5e7eb; --edge: #d1d5db; --card: #f6f8fa; --focus: #0969da; }
@media (prefers-color-scheme: dark) {
:root { --bg: #0d1117; --ink: #f0f6fc; --muted: #9da7b3; --grid: #21262d; --edge: #30363d; --card: #161b22; --focus: #58a6ff; }
}
* { box-sizing: border-box; }
body { margin: 0; padding: 16px; background: var(--bg); color: var(--ink); font: 14px/1.45 -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; }
main { max-width: 1100px; margin: 0 auto; }
h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 17px; margin: 28px 0 8px; }
p, li { max-width: 80ch; }
.muted { color: var(--muted); }
.controls { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; margin: 10px 0; }
.controls label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
button, select { font: inherit; color: var(--ink); background: var(--card); border: 1px solid var(--edge); border-radius: 6px; padding: 4px 10px; cursor: pointer; }
button:focus-visible, select:focus-visible, input:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.legend { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 4px 16px; margin: 8px 0 12px; }
.legend label { display: flex; align-items: center; gap: 8px; }
.legend .only { margin-left: auto; font-size: 12px; padding: 1px 8px; }
.legend svg { flex: none; }
.chart-wrap { position: relative; overflow-x: auto; }
svg.chart { display: block; max-width: 100%; height: auto; font-family: inherit; }
.tip { position: absolute; pointer-events: none; background: var(--card); border: 1px solid var(--edge); border-radius: 6px; padding: 8px 10px; font-size: 12px; line-height: 1.4; box-shadow: 0 2px 8px rgba(0,0,0,.2); max-width: 320px; display: none; z-index: 2; }
.tip b { display: block; margin-bottom: 2px; }
table { border-collapse: collapse; margin: 8px 0; font-size: 13px; }
th, td { border: 1px solid var(--edge); padding: 4px 8px; text-align: right; vertical-align: top; }
th:first-child, td:first-child { text-align: left; }
thead th { background: var(--card); }
.sr { position: absolute; left: -9999px; }
details { margin: 10px 0; }
summary { cursor: pointer; }
code { background: var(--card); padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<main>
<h1>JSON-RPC.NET benchmark explorer</h1>
<p class="muted">The data behind the README charts, data revision <code>697798ae199d</code>. Every range is the observed low and high over the stated runs, never a confidence interval. Hover, tap or tab to a marker for the exact values. The README stays complete without this page. <a href="https://github.com/Astn/JSON-RPC.NET#benchmarks">Back to the README</a>.</p>
<h2 id="sweep-title"></h2>
<p id="sweep-caption" class="muted"></p>
<div class="controls" role="group" aria-label="axes">
<label><input type="radio" name="scale" value="log" checked> log axis</label>
<label><input type="radio" name="scale" value="linear"> linear axis</label>
<button id="fit" type="button" title="Fit the axis to the selected series; the axis otherwise stays fixed while toggling">fit selected series</button>
<button id="reset-axis" type="button">full axis</button>
<button id="all" type="button">show all</button>
<button id="download" type="button">download data (JSON)</button>
</div>
<div id="legend" class="legend" role="group" aria-label="series"></div>
<div class="chart-wrap">
<svg id="sweep" class="chart" role="img" aria-labelledby="sweep-title sweep-caption"></svg>
<div id="tip" class="tip" role="status" aria-live="polite"></div>
</div>
<h2>All series at one connection count</h2>
<div class="controls">
<label>connections <select id="conn"></select></label>
</div>
<table id="at-conn"><caption class="sr">Every series at the chosen connection count</caption></table>
<details>
<summary>Full sweep table: low, median, high and every observation per cell</summary>
<table id="sweep-table"></table>
</details>
<h2>Snapshot sets from the README</h2>
<p class="muted">These are the tables the README publishes, with their measurement conditions. They are separate sessions from the sweep, so their absolute figures are not comparable with it.</p>
<div id="snapshots"></div>
</main>
<script id="data" type="application/json">{"families":{"grpc":"#d55e00","ours":"#009e73","scale":"#767676","theirs":"#0072b2"},"familyNames":{"grpc":"gRPC for .NET","ours":"JSON-RPC.Net","scale":"reference","theirs":"StreamJsonRpc"},"machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","revision":"697798ae199d","runtime":".NET 10.0.12, Release, Server GC, built-in serializer","sets":{"async1":{"conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","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"]}],"harness":"TestServer_Console --async 3 1","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 2 runs of each row","run_count":2,"runtime":".NET 10.0.12, Release, Server GC","series":[{"bytes":6,"cpu":{"process":3.3,"system":3.3},"family":"ours","high":2520838,"id":"async1-sync-none","label":"synchronous methods, None","low":2513106,"median":2516972,"n":2,"published":"2.51 M to 2.52 M","status":"range","values":[2520838,2513106]},{"bytes":251,"cpu":{"process":3.3,"system":3.3},"family":"ours","high":1981182,"id":"async1-task-flow","label":"Task\u003cT>, Flow","low":1978020,"median":1979601,"n":2,"published":"1.98 M","row":"`Task\u003cT>`, Flow","status":"range","values":[1981182,1978020]},{"bytes":67,"cpu":{"process":3.4,"system":3.4},"family":"ours","high":2655947,"id":"async1-task-none","label":"Task\u003cT>, None","low":2629355,"median":2642651,"n":2,"published":"2.63 M to 2.66 M","row":"`Task\u003cT>`, None","status":"range","values":[2655947,2629355]},{"bytes":190,"cpu":{"process":3.2,"system":3.2},"family":"ours","high":2059920,"id":"async1-valuetask-flow","label":"ValueTask\u003cT>, Flow","low":2047241,"median":2053580.5,"n":2,"published":"2.05 M to 2.06 M","row":"`ValueTask\u003cT>`, Flow","status":"range","values":[2059920,2047241]},{"bytes":6,"cpu":{"process":3.3,"system":3.3},"family":"ours","high":2818163,"id":"async1-valuetask-none","label":"ValueTask\u003cT>, None","low":2769948,"median":2794055.5,"n":2,"published":"2.77 M to 2.82 M","row":"`ValueTask\u003cT>`, None","status":"range","values":[2818163,2769948]},{"bytes":740,"cpu":{"process":18.1,"system":18.1},"family":"ours","high":589434,"id":"async1-yield-flow","label":"yieldsOnce, Flow","low":563130,"median":576282,"n":2,"published":"563 k to 589 k","row":"`yieldsOnce`, Flow","status":"range","values":[563130,589434]},{"bytes":548,"cpu":{"process":21.2,"system":21.2},"family":"ours","high":565206,"id":"async1-yield-none","label":"yieldsOnce, None","low":543546,"median":554376,"n":2,"published":"544 k to 565 k","row":"`yieldsOnce`, None","status":"range","values":[543546,565206]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","title":"ProcessAsync, one awaited worker, by registration","workers":1,"workload":"as asyncn, one worker"},"asyncn":{"conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","groups":[{"heading":"methods that complete inline, RpcContextFlow.None","rule":false,"series":["asyncn-sync-none","asyncn-task-none","asyncn-valuetask-none"]},{"heading":"methods that complete inline, RpcContextFlow.Flow","rule":false,"series":["asyncn-task-flow","asyncn-valuetask-flow"]},{"heading":"one real suspension per request (yieldsOnce)","rule":true,"series":["asyncn-yield-none","asyncn-yield-flow"]}],"harness":"TestServer_Console --async 3 32","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 2 runs of each row","run_count":2,"runtime":".NET 10.0.12, Release, Server GC","series":[{"bytes":6,"cpu":{"process":93.4,"system":93.4},"family":"ours","high":28656762,"id":"asyncn-sync-none","label":"synchronous methods, None","low":26710958,"median":27683860,"n":2,"note":"6 B per request, the StringMe result string","published":"26.7 M to 28.7 M","status":"range","values":[26710958,28656762]},{"bytes":251,"cpu":{"process":79.8,"system":79.8},"family":"ours","high":27572270,"id":"asyncn-task-flow","label":"Task\u003cT>, Flow","low":27004517,"median":27288393.5,"n":2,"note":"251 B per request","published":"27 M to 27.6 M","row":"`Task\u003cT>`, Flow","status":"range","values":[27004517,27572270]},{"bytes":67,"cpu":{"process":93,"system":93},"family":"ours","high":35453995,"id":"asyncn-task-none","label":"Task\u003cT>, None","low":34390026,"median":34922010.5,"n":2,"note":"67 B per request, the service's own Task.FromResult","published":"34.4 M to 35.5 M","row":"`Task\u003cT>`, None","status":"range","values":[35453995,34390026]},{"bytes":190,"cpu":{"process":84.7,"system":84.7},"family":"ours","high":31438045,"id":"asyncn-valuetask-flow","label":"ValueTask\u003cT>, Flow","low":28613257,"median":30025651,"n":2,"note":"190 B per request","published":"28.6 M to 31.4 M","row":"`ValueTask\u003cT>`, Flow","status":"range","values":[28613257,31438045]},{"bytes":6,"cpu":{"process":96.9,"system":96.9},"family":"ours","high":40521407,"id":"asyncn-valuetask-none","label":"ValueTask\u003cT>, None","low":36255749,"median":38388578,"n":2,"note":"6 B per request","published":"36.3 M to 40.5 M","row":"`ValueTask\u003cT>`, None","status":"range","values":[36255749,40521407]},{"bytes":377,"cpu":{"process":89.8,"system":89.8},"family":"ours","high":2775513,"id":"asyncn-yield-flow","label":"yieldsOnce, Flow","low":2739377,"median":2757445,"n":2,"note":"377 B per request","published":"2.74 M to 2.78 M","row":"`yieldsOnce`, Flow","status":"range","values":[2775513,2739377]},{"bytes":802,"cpu":{"process":91.5,"system":91.5},"family":"ours","high":2552560,"id":"asyncn-yield-none","label":"yieldsOnce, None","low":2548190,"median":2550375,"n":2,"note":"802 B per request","published":"2.55 M","row":"`yieldsOnce`, None","status":"range","values":[2552560,2548190]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","title":"ProcessAsync, 32 awaited workers, by registration","workers":32,"workload":"the five benchmark requests (yieldsOnce: one request) through the byte-level JsonRpcProcessor.ProcessAsync from Task.Run workers that await each call"},"compare":{"alt":"JSON-RPC.Net vs StreamJsonRpc vs gRPC for .NET at 32 connections: low-to-high intervals on a log axis, grouped by library, with the in-process paths under a rule","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","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"]}],"harness":"TestServer_Console --compare 3 (32 connections)","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 2 runs; a single figure is one value that every run rounded to","run_count":2,"runtime":".NET 10.0.12, Release, Server GC","series":[{"cpu":{"clients":17,"process":94.6,"system":94.7},"family":"ours","high":16778975,"id":"ours-tcp-jsonrpc","label":"over Kestrel TCP","low":16438116,"median":16608545.5,"n":2,"note":"raw documents","published":"16.4 M to 16.8 M","row":"JSON-RPC.Net over Kestrel TCP, raw documents","status":"range","values":[16778975,16438116]},{"cpu":{"clients":10.1,"process":86.8,"system":86.8},"family":"theirs","high":878656,"id":"sjr-newline-stj","label":"over Kestrel TCP","low":846928,"median":862792,"n":2,"note":"newline framing, System.Text.Json formatter","published":"847 k to 879 k","row":"StreamJsonRpc over Kestrel TCP, newline framing, System.Text.Json formatter","status":"range","values":[846928,878656]},{"cpu":{"clients":10.1,"process":89.4,"system":89.4},"family":"theirs","high":1151116,"id":"sjr-cl-stj","label":"over Kestrel TCP","low":1060037,"median":1105576.5,"n":2,"note":"Content-Length framing, System.Text.Json formatter","published":"1.06 M to 1.15 M","row":"StreamJsonRpc over Kestrel TCP, `Content-Length` framing, System.Text.Json formatter","status":"range","values":[1060037,1151116]},{"cpu":{"clients":7.8,"process":82,"system":82},"family":"theirs","high":583191,"id":"sjr-cl-newtonsoft","label":"over Kestrel TCP","low":560352,"median":571771.5,"n":2,"note":"Content-Length framing, Json.NET formatter (default)","published":"560 k to 583 k","row":"StreamJsonRpc over Kestrel TCP, `Content-Length` framing, Json.NET formatter (its default)","status":"range","values":[560352,583191]},{"cpu":{"process":92.7,"system":92.7},"family":"grpc","high":166452,"id":"grpc-unary","label":"unary calls","low":157574,"median":162013,"n":2,"note":"HTTP/2 + protobuf, 32 channels \u00d7 256 in flight","published":"158 k to 166 k","row":"gRPC for .NET, unary calls over HTTP/2 (Grpc.Net.Client, 32 channels \u00d7 256 in flight)","status":"range","values":[157574,166452]},{"cpu":{"process":96.5,"system":96.5},"family":"grpc","high":732602,"id":"grpc-stream","label":"bidirectional stream","low":703425,"median":718013.5,"n":2,"note":"one stream per channel, 256 in flight, batched writes","published":"703 k to 733 k","row":"gRPC for .NET, one bidirectional stream per channel, 256 in flight, batched writes","status":"range","values":[703425,732602]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["Same five calls, same Kestrel, 32 connections or channels with 256 requests in flight each, two 3 s runs.","Every request carries \"jsonrpc\":\"2.0\" (StreamJsonRpc requires it). Intervals span the runs; a dot is a single value."],"title":"JSON-RPC.Net vs StreamJsonRpc vs gRPC for .NET, 32 connections","versions":"StreamJsonRpc 2.25.29, gRPC for .NET 2.84.0","workers":32,"workload":"the same five calls on the same Kestrel; 32 connections or channels with 256 requests in flight each; every request carries \"jsonrpc\":\"2.0\""},"headline":{"alt":"JSON-RPC.Net 1.2.3 and 2.0 on one machine, low to high over the runs of one job on 2026-09-28: 1.29 M to 2.41 M requests per second through the 1.2.3 string API, 9.83 M to 11.1 M through the same API on 2.0, and 44.2 M to 45.7 M and 36.3 M to 40.5 M through the 2.0 byte entry points","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","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"]}],"harness":"benchmarks/Baseline (1.2.3 from NuGet), TestServer_Console menu entry t, --sync 3 and --async 3 32","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"2 runs of benchmarks/Baseline and 3 runs of the t entry, each at its best batch size by median; 2 runs of --sync and 2 runs of --async at 32; low to high over the runs","run_count":9,"runtime":".NET 10.0.12, Release, Server GC","series":[{"batch":6000,"cpu":{"process":68.5,"system":68.5},"family":"scale","high":2412083,"id":"headline-123-string","label":"Task\u003cstring> Process(string), thread pool","low":1289438,"median":1850760.5,"n":2,"note":"best batch size, 6,000; Json.NET on every request","published":"1.29 M to 2.41 M","row":"1.2.3, `Task\u003cstring> Process(string)`, thread pool, best batch size","status":"range","values":[1289438,2412083]},{"against":"6.0\u00d7","batch":6000,"cpu":{"process":87.5,"system":87.5},"family":"ours","high":11144579,"id":"headline-20-string","label":"Task\u003cstring> Process(string), thread pool","low":9825595,"median":11110511,"n":3,"note":"best batch size, 6,000; the 1.x overloads, kept for compatibility","published":"9.83 M to 11.1 M","row":"2.0, the same string API and the same loop","status":"range","values":[11110511,11144579,9825595]},{"against":"24.3\u00d7","cpu":{"process":91.4,"system":91.4},"family":"ours","high":45671189,"id":"headline-20-sync","label":"Process(bytes), 32 dedicated threads","low":44239898,"median":44955543.5,"n":2,"note":"the library alone; 0 B per numeric request","published":"44.2 M to 45.7 M","row":"2.0, `Process(bytes)`, 32 dedicated threads","status":"range","values":[44239898,45671189]},{"against":"20.7\u00d7","bytes":6,"cpu":{"process":96.9,"system":96.9},"family":"ours","high":40521407,"id":"headline-20-async","label":"ProcessAsync(bytes), 32 awaited workers","low":36255749,"median":38388578,"n":2,"note":"ValueTask\u003cT> methods, RpcContextFlow.None; 6 B per request","published":"36.3 M to 40.5 M","row":"2.0, `ProcessAsync(bytes)`, 32 awaited workers","status":"range","values":[36255749,40521407]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["The same five requests on one machine in one job, 2026-09-28: AMD EPYC 7R13, 32 cores, .NET 10, Server GC.","String rows: the best batch size of the thread-pool loop. Byte rows: 32 dedicated threads or 32 awaited workers in a loop."],"title":"JSON-RPC.Net 1.2.3 and 2.0 on one machine","workers":32,"workload":"the five benchmark requests; the string rows are the best batch size of the thread-pool loop, the byte rows a loop on 32 dedicated threads or 32 awaited workers"},"inprocess":{"alt":"In-process paths: JSON-RPC.Net direct call, StreamJsonRpc Pipe pair and StreamJsonRpc typed proxy, as low-to-high intervals on a log axis","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","groups":[{"heading":"","rule":false,"series":["ours-direct-1","sjr-pipe","sjr-proxy"]}],"harness":"TestServer_Console --compare 3 (32 connections)","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 2 runs; a single figure is one value that every run rounded to","run_count":2,"runtime":".NET 10.0.12, Release, Server GC","series":[{"cpu":{"process":3.1,"system":3.1},"family":"ours","high":3008500,"id":"ours-direct-1","label":"JSON-RPC.Net, direct call, 1 thread","low":2974336,"median":2991418,"n":2,"note":"bytes in, bytes out, no transport","published":"2.97 M to 3.01 M","row":"JSON-RPC.Net in-process, 1 thread (direct call, bytes in, bytes out)","status":"range","values":[3008500,2974336]},{"cpu":{"process":19.8,"system":19.8},"family":"theirs","high":153717,"id":"sjr-pipe","label":"StreamJsonRpc, Pipe pair, 1 client","low":133143,"median":143430,"n":2,"note":"newline framing, System.Text.Json, 256 pipelined","published":"133 k to 154 k","row":"StreamJsonRpc in-process, 1 client over a `Pipe` pair, newline framing, System.Text.Json formatter, 256 pipelined","status":"range","values":[153717,133143]},{"cpu":{"process":12.8,"system":12.8},"family":"theirs","high":56199,"id":"sjr-proxy","label":"StreamJsonRpc, typed proxy","latency_us":[18,19],"low":53734,"median":54966.5,"n":2,"note":"sequential await per call, 18 to 19 \u00b5s per round trip","published":"53.7 k to 56.2 k","row":"StreamJsonRpc typed proxy, sequential `await` per call, in-process pipes","rpc_note":"18 to 19 \u00b5s per round trip","status":"range","values":[56199,53734]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["No network. A direct call, a Pipe pair and a typed proxy cross different boundaries, so these rows are not a","like-for-like comparison with each other or with the transport rows. Intervals span two 3 s runs."],"title":"In-process paths: different execution boundaries","workers":32,"workload":"the same five calls with no network: a direct call, a Pipe pair, a typed proxy"},"kestrel":{"alt":"JSON-RPC.Net by transport: HTTP single, HTTP batch of 100 and TCP pipelined, as low-to-high intervals on a log axis, with the in-process figure for scale","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","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-n"]}],"harness":"TestServer_Console --kestrel 3 and --kestrel 3 async (32 clients)","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over the runs: 2 runs of --kestrel for the EnableAsyncMethods = false rows, 2 runs of --kestrel async for the EnableAsyncMethods = true rows","run_count":4,"runtime":".NET 10.0.12, Release, Server GC","series":[{"cpu":{"process":92.8,"system":92.8},"family":"scale","high":49559560,"id":"ours-inproc-n","label":"in-process, 32 threads","low":47335159,"median":48447359.5,"n":2,"note":"the same requests through the byte entry point","published":"47.3 M to 49.6 M","status":"range","values":[49559560,47335159]},{"cpu":{"process":90.5,"system":90.6},"family":"ours","high":162103,"id":"ours-http-1","label":"HTTP, 1 request per POST","latency_us":[197,205],"low":155808,"median":158955.5,"n":2,"note":"each client awaits one POST at a time; 197 to 205 \u00b5s per round trip","published":"156 k to 162 k","row_note":"197 to 205 \u00b5s per round trip per client depending on the run; HTTP/1.1 request-response is the cost, not the server","status":"range","values":[162103,155808]},{"cpu":{"process":91.6,"system":91.6},"family":"ours","high":14757580,"id":"ours-http-100","label":"HTTP, batch of 100 per POST","low":14356502,"median":14557041,"n":2,"note":"one POST outstanding per client, 100 RPCs in it","published":"14.4 M to 14.8 M","status":"range","values":[14757580,14356502]},{"cpu":{"clients":19,"process":95.3,"system":95.3},"family":"ours","high":18506274,"id":"ours-tcp","label":"TCP, 256 pipelined","low":18454461,"median":18480367.5,"n":2,"note":"JsonRpcConnectionHandler, 256 requests in flight per connection","published":"18.5 M","row_note":"ring-buffer clients, one thread each, streaming framer","status":"range","values":[18454461,18506274]},{"cpu":{"clients":17.9,"process":95.4,"system":95.4},"family":"ours","high":17080213,"id":"ours-tcp-async-inline","label":"TCP, 256 pipelined, methods that complete inline","low":17024435,"median":17052324,"n":2,"note":"--kestrel 3 async","published":"17 M to 17.1 M","row":"TCP, 256 pipelined, `EnableAsyncMethods = true`, methods that complete inline","row_note":"`--kestrel 3 async`, two runs","status":"range","values":[17024435,17080213]},{"cpu":{"clients":12.1,"process":94.8,"system":94.8},"family":"ours","high":2867023,"id":"ours-tcp-async-yield","label":"TCP, 256 pipelined, methods that suspend once","low":2863591,"median":2865307,"n":2,"note":"five async Task\u003cT> methods awaiting Task.Yield(); documents are sequential per connection","published":"2.86 M to 2.87 M","row":"TCP, 256 pipelined, `EnableAsyncMethods = true`, methods that suspend once","row_note":"five `async Task\u003cT>` methods awaiting `Task.Yield()`","status":"range","values":[2867023,2863591]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["AustinHarris.JsonRpc.AspNetCore on Kestrel, loopback, 32 clients on the server's 32 cores; 3 s runs, intervals span them.","HTTP clients await one POST at a time; TCP keeps 256 requests in flight per connection."],"title":"JSON-RPC.Net by transport","workers":32,"workload":"AustinHarris.JsonRpc.AspNetCore on Kestrel, loopback, 32 clients on the server's 32 cores"},"legacy":{"conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","harness":"TestServer_Console, menu entry t with stdout redirected (BenchmarkRunner.Benchmark)","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 3 runs","run_count":3,"runtime":".NET 10.0.12, Release, Server GC","series":[{"family":"ours","id":"legacy-string","label":"JSON-RPC.Net, 1.x string overloads","points":[{"cpu":{"process":76.7,"system":76.7},"high":1435139,"low":1078909,"median":1086828,"n":3,"published":"1.08 M to 1.44 M","status":"range","values":[1086828,1435139,1078909]},{"cpu":{"process":89.7,"system":89.7},"high":1512488,"low":1434129,"median":1501647,"n":3,"published":"1.43 M to 1.51 M","status":"range","values":[1512488,1434129,1501647]},{"cpu":{"process":85.9,"system":85.9},"high":4103938,"low":3724002,"median":3922107,"n":3,"published":"3.72 M to 4.1 M","status":"range","values":[3922107,4103938,3724002]},{"cpu":{"process":86.2,"system":86.4},"high":7803867,"low":7551462,"median":7615069,"n":3,"published":"7.55 M to 7.8 M","status":"range","values":[7551462,7803867,7615069]},{"cpu":{"process":87.5,"system":87.5},"high":11144579,"low":9825595,"median":11110511,"n":3,"published":"9.83 M to 11.1 M","status":"range","values":[11110511,11144579,9825595]},{"cpu":{"process":81.9,"system":81.9},"high":10782349,"low":10284576,"median":10416737,"n":3,"published":"10.3 M to 10.8 M","status":"range","values":[10782349,10284576,10416737]},{"cpu":{"process":70.9,"system":71},"high":6778844,"low":5958403,"median":5963409,"n":3,"published":"5.96 M to 6.78 M","status":"range","values":[5958403,5963409,6778844]},{"cpu":{"process":76,"system":76},"high":6145911,"low":3397534,"median":5607426,"n":3,"published":"3.4 M to 6.15 M","status":"range","values":[6145911,3397534,5607426]}]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","title":"Legacy string API through the thread pool, by batch size","workload":"the five benchmark requests through the 1.x Task\u003cstring> Process(string) overload, submitted in batches from every core through Parallel.For","x":{"name":"batch size","values":[50,100,300,1200,6000,36000,252000,2016000]}},"sweep":{"alt":"Every library and transport by client connections, 1 to 32: three panels on a shared log axis, one per library, with a marker shape and dash per setting and whiskers spanning five runs","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","harness":"TestServer_Console --sweep 2; one file per run, matched by the pattern in runs","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","pipeline":256,"policy":"low, high and median over 5 runs; each run contributes one observation per cell","run_count":5,"run_files":["sweep.json","sweep-2.json","sweep-3.json","sweep-4.json","sweep-5.json"],"runs":"sweep*.json","runs_text":"5 runs of 2 s per point; whiskers span the runs","runtime":".NET 10.0.12, Release, Server GC","seconds_per_cell":2,"series":[{"concurrency":"256 requests in flight per connection","family":"ours","id":"sweep-ours-tcp","label":"TCP","name":"JSON-RPC.Net, TCP","points":[{"cpu":{"clients":1.2,"process":10.8,"system":10.8},"high":1663416,"low":1504315,"median":1582632,"n":5,"status":"range","values":[1504315,1581792,1582632,1663416,1597854]},{"cpu":{"clients":2.4,"process":18.1,"system":18.1},"high":2906285,"low":2264852,"median":2864827,"n":5,"status":"range","values":[2807580,2891600,2864827,2906285,2264852]},{"cpu":{"clients":4.9,"process":31.4,"system":31.4},"high":5123054,"low":4709242,"median":4982397,"n":5,"status":"range","values":[5123054,4728503,4709242,5025529,4982397]},{"cpu":{"clients":7.9,"process":53.4,"system":53.4},"high":8541684,"low":8355818,"median":8408546,"n":5,"status":"range","values":[8436939,8408546,8355818,8382932,8541684]},{"cpu":{"clients":15.1,"process":89.8,"system":89.8},"high":13923831,"low":13672167,"median":13838572,"n":5,"status":"range","values":[13838572,13792074,13672167,13923831,13909240]},{"cpu":{"clients":17.7,"process":96.7,"system":96.7},"high":17762271,"low":17341543,"median":17518939,"n":5,"status":"range","values":[17455330,17518939,17341543,17612181,17762271]}],"style":0},{"concurrency":"one POST outstanding per connection, 100 RPCs in it","family":"ours","id":"sweep-ours-http-100","label":"HTTP, batch of 100 per POST","name":"JSON-RPC.Net, HTTP, batch of 100 per POST","points":[{"cpu":{"process":10.6,"system":10.6},"high":780521,"low":711106,"median":743574,"n":5,"status":"range","values":[747452,743574,780521,730180,711106]},{"cpu":{"process":16.7,"system":16.7},"high":1575796,"low":1536182,"median":1544718,"n":5,"status":"range","values":[1575796,1544556,1546103,1544718,1536182]},{"cpu":{"process":28.6,"system":28.6},"high":3049007,"low":2998846,"median":3018942,"n":5,"status":"range","values":[3018942,3011600,3032005,3049007,2998846]},{"cpu":{"process":48.2,"system":48.2},"high":5528558,"low":5444182,"median":5486596,"n":5,"status":"range","values":[5528558,5500819,5480732,5486596,5444182]},{"cpu":{"process":80.4,"system":80.4},"high":9423705,"low":9282494,"median":9347086,"n":5,"status":"range","values":[9282494,9298140,9347086,9423705,9353620]},{"cpu":{"process":93.3,"system":93.3},"high":14604824,"low":14356886,"median":14568739,"n":5,"status":"range","values":[14568739,14559938,14356886,14590783,14604824]}],"style":1},{"concurrency":"one POST outstanding per connection","family":"ours","id":"sweep-ours-http-1","label":"HTTP, 1 request per POST","name":"JSON-RPC.Net, HTTP, 1 request per POST","points":[{"cpu":{"process":9.8,"system":9.8},"high":15268,"low":14258,"median":14732,"n":5,"status":"range","values":[15268,14732,14971,14549,14258]},{"cpu":{"process":15.4,"system":15.4},"high":29774,"low":28591,"median":28672,"n":5,"status":"range","values":[28672,28591,29265,29774,28622]},{"cpu":{"process":26.1,"system":26.1},"high":55763,"low":54512,"median":54983,"n":5,"status":"range","values":[54983,54848,54512,55003,55763]},{"cpu":{"process":44.3,"system":44.3},"high":98859,"low":97476,"median":98738,"n":5,"status":"range","values":[98843,98859,98738,98651,97476]},{"cpu":{"process":75.1,"system":75.1},"high":168059,"low":164467,"median":166437,"n":5,"status":"range","values":[168059,166437,164889,166932,164467]},{"cpu":{"process":94.8,"system":94.8},"high":290499,"low":287938,"median":289276,"n":5,"status":"range","values":[287938,288711,289588,290499,289276]}],"style":2},{"concurrency":"256 requests in flight per connection","family":"theirs","id":"sweep-sjr-newline-stj","label":"TCP, newline + System.Text.Json","name":"StreamJsonRpc, TCP, newline + System.Text.Json","points":[{"cpu":{"clients":2.5,"process":20.3,"system":20.3},"high":195042,"low":169122,"median":181536,"n":5,"status":"range","values":[195042,169122,181536,192370,180901]},{"cpu":{"clients":5.1,"process":36.6,"system":36.7},"high":310815,"low":290819,"median":296378,"n":5,"status":"range","values":[302641,310815,296378,290819,292461]},{"cpu":{"clients":10.3,"process":66.7,"system":66.7},"high":481397,"low":462815,"median":476216,"n":5,"status":"range","values":[481397,476216,478417,466228,462815]},{"cpu":{"clients":11.9,"process":85.2,"system":85.2},"high":791998,"low":761947,"median":768721,"n":5,"status":"range","values":[765565,761947,791998,768721,776653]},{"cpu":{"clients":10.9,"process":87.1,"system":87.1},"high":908097,"low":858189,"median":881160,"n":5,"status":"range","values":[908097,858189,902865,866567,881160]},{"cpu":{"clients":10.1,"process":89.5,"system":89.5},"high":1008400,"low":924264,"median":971720,"n":5,"status":"range","values":[991712,954696,1008400,924264,971720]}],"style":0},{"concurrency":"256 requests in flight per connection","family":"theirs","id":"sweep-sjr-cl-stj","label":"TCP, Content-Length + System.Text.Json","name":"StreamJsonRpc, TCP, Content-Length + System.Text.Json","points":[{"cpu":{"clients":2.5,"process":20.5,"system":20.5},"high":210829,"low":196223,"median":197636,"n":5,"status":"range","values":[197627,196223,210829,197636,203954]},{"cpu":{"clients":4.9,"process":37,"system":37},"high":352609,"low":337259,"median":341014,"n":5,"status":"range","values":[341343,339821,352609,341014,337259]},{"cpu":{"clients":10,"process":67.8,"system":67.8},"high":563704,"low":552090,"median":557259,"n":5,"status":"range","values":[552293,562128,557259,563704,552090]},{"cpu":{"clients":11.5,"process":90.4,"system":90.4},"high":968434,"low":943109,"median":953507,"n":5,"status":"range","values":[953507,968434,951676,943109,959551]},{"cpu":{"clients":10.7,"process":92.9,"system":92.9},"high":1265533,"low":1117138,"median":1255931,"n":5,"status":"range","values":[1265533,1260862,1255931,1117138,1242741]},{"cpu":{"clients":10.1,"process":91.6,"system":91.6},"high":1250026,"low":1115974,"median":1233730,"n":5,"status":"range","values":[1233730,1222980,1249031,1115974,1250026]}],"style":1},{"concurrency":"256 requests in flight per connection","family":"theirs","id":"sweep-sjr-cl-newtonsoft","label":"TCP, Content-Length + Json.NET","name":"StreamJsonRpc, TCP, Content-Length + Json.NET","points":[{"cpu":{"clients":2.3,"process":19.2,"system":19.2},"high":139087,"low":122182,"median":133171,"n":5,"status":"range","values":[131421,136732,122182,133171,139087]},{"cpu":{"clients":4.7,"process":35,"system":35},"high":220368,"low":204910,"median":213573,"n":5,"status":"range","values":[210770,213573,204910,217147,220368]},{"cpu":{"clients":9.6,"process":64.3,"system":64.3},"high":369044,"low":356420,"median":363840,"n":5,"status":"range","values":[364211,369044,356420,363840,358624]},{"cpu":{"clients":11.8,"process":86.4,"system":86.4},"high":642518,"low":616843,"median":634055,"n":5,"status":"range","values":[642518,616843,632229,634055,636066]},{"cpu":{"clients":10.1,"process":87.2,"system":87.2},"high":733715,"low":660979,"median":686417,"n":5,"status":"range","values":[665977,725538,686417,660979,733715]},{"cpu":{"clients":9.1,"process":84.8,"system":84.8},"high":740627,"low":618631,"median":668408,"n":5,"status":"range","values":[655445,740627,668408,618631,717800]}],"style":2},{"concurrency":"256 calls in flight per channel","family":"grpc","id":"sweep-grpc-unary","label":"unary","name":"gRPC for .NET, unary","points":[{"cpu":{"process":82.5,"system":82.6},"high":142853,"low":136259,"median":137954,"n":5,"status":"range","values":[137954,137109,138886,142853,136259]},{"cpu":{"process":94.1,"system":94.1},"high":255854,"low":252190,"median":254309,"n":5,"status":"range","values":[254309,255854,252190,253783,255434]},{"cpu":{"process":93.3,"system":93.3},"high":339254,"low":328837,"median":336472,"n":5,"status":"range","values":[328837,339254,336472,338634,335192]},{"cpu":{"process":92.5,"system":92.5},"high":356705,"low":337860,"median":350513,"n":5,"status":"range","values":[350513,356528,356705,346085,337860]},{"cpu":{"process":87.9,"system":87.9},"high":323245,"low":308571,"median":314681,"n":5,"status":"range","values":[313991,308571,314681,322720,323245]},{"cpu":{"process":88.4,"system":88.4},"high":289596,"low":269442,"median":274986,"n":5,"status":"range","values":[289596,269442,275939,274986,272509]}],"style":0},{"concurrency":"one stream per channel, 256 in flight, batched writes","family":"grpc","id":"sweep-grpc-stream","label":"bidirectional stream","name":"gRPC for .NET, bidirectional stream","points":[{"cpu":{"process":24.7,"system":24.7},"high":36814,"low":35607,"median":36543,"n":5,"status":"range","values":[36814,36543,35607,36219,36574]},{"cpu":{"process":41.2,"system":41.2},"high":70267,"low":69178,"median":69901,"n":5,"status":"range","values":[70148,69822,69178,69901,70267]},{"cpu":{"process":68.2,"system":68.2},"high":125542,"low":125211,"median":125414,"n":5,"status":"range","values":[125211,125542,125256,125414,125456]},{"cpu":{"process":89.8,"system":89.8},"high":228992,"low":223925,"median":228506,"n":5,"status":"range","values":[228992,228889,228506,223925,227039]},{"cpu":{"process":96.5,"system":96.5},"high":437574,"low":423644,"median":434284,"n":5,"status":"range","values":[437574,434284,436949,432957,423644]},{"cpu":{"process":97.6,"system":97.6},"high":913031,"low":893246,"median":901600,"n":5,"status":"range","values":[893246,913031,893936,901600,906325]}],"style":1}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["Same five calls, same Kestrel, clients on the server's 32 cores, 2026-09-28. TCP and gRPC keep 256 requests in flight","per connection; HTTP clients await one POST at a time (1 or 100 RPCs). 5 runs of 2 s per point; whiskers span the runs."],"title":"Every library and transport, by client connections","workload":"the same five calls on the same Kestrel, clients on the server's 32 cores; each cell warmed for 2 s, then timed","x":{"name":"client connections (gRPC: channels)","values":[1,2,4,8,16,32]}},"sync":{"alt":"JSON-RPC.Net alone, by worker threads: aggregate requests per second as a low-to-high band, and the reported ns per request per thread","conditions":"Hugging Face Jobs cpu-performance, the whole 32-core host (cgroup quota 32 of 32 CPUs), one job on 2026-09-28","cores":32,"date":"2026-09-28","harness":"TestServer_Console --sync 3 (1, 2, 4, ... threads up to 32)","machine":"AMD EPYC 7R13 Processor, 32 cores of 32 host CPUs, cgroup quota, Ubuntu 24.04.5 LTS, .NET 10.0.12, Release, Server GC","policy":"low, high and median over 2 runs; ns per request per thread is the median of the runs' reported figures","run_count":2,"runtime":".NET 10.0.12, Release, Server GC","series":[{"family":"ours","id":"ours-sync","label":"JSON-RPC.Net, byte entry point","note":"0 bytes for numeric shapes, one string for `StringMe`","points":[{"cpu":{"process":3.2,"system":3.2},"high":3175966,"low":3174372,"median":3175169,"n":2,"ns":315,"published":"3.17 M to 3.18 M","status":"range","values":[3175966,3174372]},{"cpu":{"process":6.3,"system":6.3},"high":4660256,"low":4310687,"median":4485471.5,"n":2,"ns":446,"published":"4.31 M to 4.66 M","status":"range","values":[4660256,4310687]},{"cpu":{"process":12.4,"system":12.4},"high":11951987,"low":10665149,"median":11308568,"n":2,"ns":355,"published":"10.7 M to 12 M","status":"range","values":[11951987,10665149]},{"cpu":{"process":24.6,"system":24.6},"high":19841401,"low":18372188,"median":19106794.5,"n":2,"ns":419,"published":"18.4 M to 19.8 M","status":"range","values":[19841401,18372188]},{"cpu":{"process":47.9,"system":47.9},"high":35823680,"low":34467034,"median":35145357,"n":2,"ns":456,"published":"34.5 M to 35.8 M","status":"range","values":[34467034,35823680]},{"cpu":{"process":91.4,"system":91.4},"high":45671189,"low":44239898,"median":44955543.5,"n":2,"ns":712,"published":"44.2 M to 45.7 M","status":"range","values":[44239898,45671189]}]}],"source":"Hugging Face job 6ab9b3936b030d633f69b0fb, commit 6c3c272","subtitle":["Byte entry point in a loop, no scheduler, no transport. AMD EPYC 7R13, 32 cores, .NET 10, Server GC, two runs."],"title":"JSON-RPC.Net alone, by worker threads","workers":32,"workload":"the five benchmark requests through the byte-level JsonRpcProcessor.Process in a loop; no scheduler, no transport","x":{"caption":"worker threads (each step doubles; 32 cores)","name":"worker threads","values":[1,2,4,8,16,32]}},"wasm":{"conditions":"a browser measurement on an 8-core desktop, not the benchmark job's host; interpreter from dotnet run, AOT from dotnet publish -c Release with the wasm-tools workload","date":"2026-09-23","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"]}],"harness":"samples/WasmHost, Run benchmark in Chrome 152","modes":[{"id":"interp","label":"interpreter","marker":"circle","policy":"one run"},{"id":"aot","label":"AOT","marker":"diamond","policy":"better of two runs"}],"policy":"interpreter: one run; AOT: the better of two runs. The interpreter-to-AOT distance is a deployment difference, not a range","series":[{"family":"scale","id":"wasm-plain-invoke","label":"invokeMethod Add","published":{"aot":"66,600","interp":"15,700"},"values":{"aot":66600,"interp":15700}},{"family":"scale","id":"wasm-plain-invoke-async","label":"invokeMethodAsync Add","published":{"aot":"59,300","interp":"14,500"},"values":{"aot":59300,"interp":14500}},{"family":"scale","id":"wasm-plain-jsexport","label":"[JSExport] AddExported, typed arguments","published":{"aot":"3,230,000","interp":"2,860,000"},"values":{"aot":3230000,"interp":2860000}},{"family":"ours","id":"wasm-rpc-invoke","label":"invokeMethod Process","published":{"aot":"33,500","interp":"6,000"},"values":{"aot":33500,"interp":6000}},{"family":"ours","id":"wasm-rpc-invoke-async","label":"invokeMethodAsync Process","published":{"aot":"36,000","interp":"5,700"},"values":{"aot":36000,"interp":5700}},{"family":"ours","id":"wasm-rpc-jsexport","label":"[JSExport] ProcessExported, strings","published":{"aot":"151,600","interp":"17,100"},"values":{"aot":151600,"interp":17100}},{"family":"ours","id":"wasm-rpc-bytes","label":"[JSExport] ProcessBytes, UTF-8 buffers","published":{"aot":"142,000","interp":"19,000"},"values":{"aot":142000,"interp":19000}},{"family":"ours","id":"wasm-rpc-invoke-100","label":"invokeMethod Process","published":{"aot":"136,000","interp":"13,000"},"values":{"aot":136000,"interp":13000}},{"family":"ours","id":"wasm-rpc-jsexport-100","label":"[JSExport] ProcessExported, strings","published":{"aot":"185,500","interp":"25,000"},"values":{"aot":185500,"interp":25000}},{"family":"ours","id":"wasm-rpc-bytes-100","label":"[JSExport] ProcessBytes, UTF-8 buffers","published":{"aot":"210,500","interp":"27,000"},"values":{"aot":210500,"interp":27000}},{"family":"scale","id":"wasm-loop","label":"ProcessMany, the string request N times","published":{"aot":"169,800","interp":"17,600"},"values":{"aot":169800,"interp":17600}}],"source":"samples/WasmHost/README.md table; commit 41371ec","subtitle":["samples/WasmHost in Chrome 152, .NET 10, 20,000 RPCs per row. Interpreter: one run. AOT: the better of two runs.","Batches are per RPC. The interpreter-to-AOT distance is a deployment difference, not a range."],"title":"In the browser: interpreter vs AOT","workload":"add(1, 2) through each interop path, 20,000 RPCs per row"}},"styles":[["","circle"],["9 5","square"],["2 4","triangle"],["12 4 2 4","diamond"]]}</script>
<script>
(function () {
"use strict";
var DATA = JSON.parse(document.getElementById("data").textContent);
var sweep = DATA.sets.sweep;
var conns = sweep.x.values;
var families = DATA.families, familyNames = DATA.familyNames, styles = DATA.styles;
var css = getComputedStyle(document.documentElement);
var theme = function (name) { return css.getPropertyValue(name).trim(); };
var NS = "http://www.w3.org/2000/svg";
function fmt(v) {
if (v >= 1e6) return (+(v / 1e6).toPrecision(3)) + " M";
if (v >= 1e3) return (+(v / 1e3).toPrecision(3)) + " k";
return String(+v.toPrecision(3));
}
function fmtAxis(v) {
if (v >= 1e6) return (v / 1e6) + " M";
if (v >= 1e3) return (v / 1e3) + " k";
return String(v);
}
function exact(v) { return Math.round(v).toLocaleString("en-US"); }
function cpuValue(p, key) { return p.cpu && p.cpu[key] != null ? p.cpu[key] + " %" : ""; }
function el(tag, attrs, parent) {
var e = document.createElementNS(NS, tag);
for (var k in attrs) e.setAttribute(k, attrs[k]);
if (parent) parent.appendChild(e);
return e;
}
function html(tag, attrs, parent, textContent) {
var e = document.createElement(tag);
for (var k in attrs) e.setAttribute(k, attrs[k]);
if (textContent != null) e.textContent = textContent;
if (parent) parent.appendChild(e);
return e;
}
function markerEl(shape, x, y, color, r, parent) {
r = r || 5;
if (shape === "circle") return el("circle", { cx: x, cy: y, r: r, fill: theme("--bg"), stroke: color, "stroke-width": 2 }, parent);
if (shape === "square") return el("rect", { x: x - r, y: y - r, width: 2 * r, height: 2 * r, fill: theme("--bg"), stroke: color, "stroke-width": 2 }, parent);
if (shape === "diamond") { var d = r * 1.3; return el("polygon", { points: [x, y - d, x + d, y, x, y + d, x - d, y].join(","), fill: color, stroke: color }, parent); }
var s = r * 1.25;
return el("polygon", { points: [x, y - s, x + s, y + s * 0.8, x - s, y + s * 0.8].join(","), fill: theme("--bg"), stroke: color, "stroke-width": 2, "stroke-linejoin": "round" }, parent);
}
// ---- state
var state = { scale: "log", visible: {}, domain: null };
sweep.series.forEach(function (s) { state.visible[s.id] = true; });
var fullDomain = (function () {
var lo = Infinity, hi = 0;
sweep.series.forEach(function (s) { s.points.forEach(function (p) { lo = Math.min(lo, p.low); hi = Math.max(hi, p.high); }); });
return [lo, hi];
})();
state.domain = fullDomain;
// ---- captions
document.getElementById("sweep-title").textContent = sweep.title;
document.getElementById("sweep-caption").textContent = sweep.workload + ". " + sweep.run_files.length + " runs of " + sweep.seconds_per_cell +
" s per point (" + sweep.run_files.join(", ") + "), " + sweep.date + ". " + sweep.conditions + ". Marker at the median, whisker low to high. " + sweep.machine + ".";
// ---- legend
var legend = document.getElementById("legend");
sweep.series.forEach(function (s) {
var color = families[s.family], st = styles[s.style % styles.length];
var label = html("label", {}, legend);
var cb = html("input", { type: "checkbox", "data-id": s.id }, label);
cb.checked = true;
cb.addEventListener("change", function () { state.visible[s.id] = cb.checked; draw(); });
var sw = el("svg", { width: 44, height: 14, "aria-hidden": "true" }, label);
el("line", { x1: 2, y1: 7, x2: 42, y2: 7, stroke: color, "stroke-width": 2, "stroke-dasharray": st[0] }, sw);
markerEl(st[1], 22, 7, color, 4, sw);
html("span", {}, label, familyNames[s.family] + ", " + s.label);
var only = html("button", { type: "button", "class": "only", title: "Show only this series" }, label, "only");
only.addEventListener("click", function (ev) {
ev.preventDefault();
sweep.series.forEach(function (o) { state.visible[o.id] = o.id === s.id; });
syncBoxes(); draw();
});
});
function syncBoxes() {
legend.querySelectorAll("input").forEach(function (cb) { cb.checked = !!state.visible[cb.getAttribute("data-id")]; });
}
document.getElementById("all").addEventListener("click", function () { sweep.series.forEach(function (s) { state.visible[s.id] = true; }); syncBoxes(); draw(); });
document.querySelectorAll("input[name=scale]").forEach(function (r) {
r.addEventListener("change", function () { state.scale = r.value; draw(); });
});
document.getElementById("fit").addEventListener("click", function () {
var lo = Infinity, hi = 0;
sweep.series.forEach(function (s) { if (!state.visible[s.id]) return; s.points.forEach(function (p) { lo = Math.min(lo, p.low); hi = Math.max(hi, p.high); }); });
if (hi > 0) { state.domain = [lo, hi]; draw(); }
});
document.getElementById("reset-axis").addEventListener("click", function () { state.domain = fullDomain; draw(); });
document.getElementById("download").addEventListener("click", function () {
var blob = new Blob([JSON.stringify(DATA, null, 2)], { type: "application/json" });
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "jsonrpc-net-benchmarks-" + DATA.revision + ".json";
document.body.appendChild(a); a.click(); a.remove();
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
});
// ---- scales
function logDomain(lo, hi) {
var d = Math.pow(10, Math.floor(Math.log10(lo)));
var l = [1, 2, 5].filter(function (m) { return d * m <= lo; }).map(function (m) { return d * m; }).pop();
var e = Math.pow(10, Math.floor(Math.log10(hi * 1.15)));
var h = [1, 2, 5, 10].map(function (m) { return e * m; }).filter(function (v) { return v >= hi * 1.15; })[0];
return [l, h];
}
function linearDomain(hi) {
var step = Math.pow(10, Math.floor(Math.log10(hi)));
if (hi / step < 2) step /= 4; else if (hi / step < 5) step /= 2;
return [0, Math.ceil(hi / step) * step];
}
function ticksFor(scale, dom) {
var out = [];
if (scale === "log") {
for (var d = Math.pow(10, Math.floor(Math.log10(dom[0]))); d <= dom[1]; d *= 10)
[1, 2, 5].forEach(function (m) { if (d * m >= dom[0] && d * m <= dom[1]) out.push({ v: d * m, major: m === 1 }); });
} else {
var span = dom[1] - dom[0], step = Math.pow(10, Math.floor(Math.log10(span)));
if (span / step < 3) step /= 2;
while (span / step > 6) step *= 2;
for (var v = 0; v <= dom[1] + 1e-9; v += step) out.push({ v: v, major: true });
}
return out;
}
// ---- draw
var svg = document.getElementById("sweep");
var tip = document.getElementById("tip");
function draw() {
while (svg.firstChild) svg.removeChild(svg.firstChild);
var W = 1060, left = 80, right = 24, top = 24, bottom = 54, H = 520;
svg.setAttribute("viewBox", "0 0 " + W + " " + H);
svg.setAttribute("width", W); svg.setAttribute("height", H);
var x0 = left, x1 = W - right, y0 = H - bottom, y1 = top;
var dom = state.scale === "log" ? logDomain(state.domain[0], state.domain[1]) : linearDomain(state.domain[1]);
var y = state.scale === "log"
? function (v) { return y0 + (y1 - y0) * (Math.log10(v) - Math.log10(dom[0])) / (Math.log10(dom[1]) - Math.log10(dom[0])); }
: function (v) { return y0 + (y1 - y0) * (v - dom[0]) / (dom[1] - dom[0]); };
var la = Math.log2(conns[0]), lb = Math.log2(conns[conns.length - 1]);
var x = function (c) { return conns.length === 1 ? (x0 + x1) / 2 : x0 + (x1 - x0) * (Math.log2(c) - la) / (lb - la); };
ticksFor(state.scale, dom).forEach(function (t) {
el("line", { x1: x0, y1: y(t.v), x2: x1, y2: y(t.v), stroke: theme("--grid"), "stroke-width": t.major ? 1 : 0.6 }, svg);
if (t.major) el("text", { x: x0 - 8, y: y(t.v) + 4, "text-anchor": "end", "font-size": 11, fill: theme("--muted") }, svg).textContent = fmtAxis(t.v);
});
conns.forEach(function (c) {
el("line", { x1: x(c), y1: y1, x2: x(c), y2: y0, stroke: theme("--grid"), "stroke-width": 0.6 }, svg);
el("text", { x: x(c), y: y0 + 18, "text-anchor": "middle", "font-size": 12, fill: theme("--ink") }, svg).textContent = c;
});
el("line", { x1: x0, y1: y0, x2: x1, y2: y0, stroke: theme("--muted") }, svg);
el("text", { x: (x0 + x1) / 2, y: H - 10, "text-anchor": "middle", "font-size": 12, fill: theme("--muted") }, svg).textContent = sweep.x.name + "; each step doubles";
el("text", { transform: "translate(18 " + (y0 + y1) / 2 + ") rotate(-90)", "text-anchor": "middle", "font-size": 12, fill: theme("--muted") }, svg).textContent =
"requests per second, " + (state.scale === "log" ? "log scale" : "linear scale");
var clip = el("clipPath", { id: "plot-clip" }, svg);
el("rect", { x: x0 - 8, y: y1 - 8, width: x1 - x0 + 16, height: y0 - y1 + 16 }, clip);
var g = el("g", { "clip-path": "url(#plot-clip)" }, svg);
sweep.series.forEach(function (s) {
if (!state.visible[s.id]) return;
var color = families[s.family], st = styles[s.style % styles.length];
el("polyline", { points: s.points.map(function (p, i) { return x(conns[i]) + "," + y(p.median); }).join(" "), fill: "none", stroke: color, "stroke-width": 2, "stroke-dasharray": st[0] }, g);
s.points.forEach(function (p, i) {
var cx = x(conns[i]);
if (p.low !== p.high) {
el("line", { x1: cx, y1: y(p.low), x2: cx, y2: y(p.high), stroke: color, "stroke-width": 3 }, g);
el("line", { x1: cx - 5, y1: y(p.low), x2: cx + 5, y2: y(p.low), stroke: color, "stroke-width": 3 }, g);
el("line", { x1: cx - 5, y1: y(p.high), x2: cx + 5, y2: y(p.high), stroke: color, "stroke-width": 3 }, g);
}
var m = markerEl(st[1], cx, y(p.median), color, 5.5, g);
m.setAttribute("tabindex", "0");
m.setAttribute("role", "img");
var text = familyNames[s.family] + ", " + s.label + " at " + conns[i] + " connections: median " + exact(p.median) +
", low " + exact(p.low) + ", high " + exact(p.high) + " RPC/s over " + p.n + " runs";
m.setAttribute("aria-label", text);
var show = function () {
tip.innerHTML = "";
html("b", {}, tip, familyNames[s.family] + ", " + s.label);
html("div", {}, tip, conns[i] + " connections; " + s.concurrency);
html("div", {}, tip, "median " + exact(p.median) + " RPC/s");
html("div", {}, tip, "low " + exact(p.low) + ", high " + exact(p.high) + " (" + p.status + ", n = " + p.n + ")");
html("div", { "class": "muted" }, tip, "runs: " + p.values.map(exact).join(", "));
html("div", { "class": "muted" }, tip, sweep.seconds_per_cell + " s per cell after a 2 s warm-up; revision " + DATA.revision);
tip.style.display = "block";
var r = svg.getBoundingClientRect(), k = r.width / W;
var px = cx * k, py = y(p.median) * k;
tip.style.left = Math.min(px + 14, r.width - 330) + "px";
tip.style.top = Math.max(0, py - 10) + "px";
};
var hide = function () { tip.style.display = "none"; };
m.addEventListener("mouseenter", show); m.addEventListener("mouseleave", hide);
m.addEventListener("focus", show); m.addEventListener("blur", hide);
m.addEventListener("click", show);
});
});
}
draw();
window.addEventListener("resize", function () { tip.style.display = "none"; });
// ---- one connection count, every series
var sel = document.getElementById("conn");
conns.forEach(function (c) { html("option", { value: c }, sel, c); });
sel.value = conns[conns.length - 1];
function atConn() {
var i = conns.indexOf(+sel.value), t = document.getElementById("at-conn");
t.innerHTML = "";
var h = html("tr", {}, html("thead", {}, t));
["series", "concurrency", "median RPC/s", "low", "high", "n", "system CPU %", "process CPU %", "client CPU %"].forEach(function (c) { html("th", { scope: "col" }, h, c); });
var b = html("tbody", {}, t);
sweep.series.forEach(function (s) {
var p = s.points[i], r = html("tr", {}, b);
html("td", {}, r, familyNames[s.family] + ", " + s.label);
html("td", { style: "text-align:left" }, r, s.concurrency);
html("td", {}, r, exact(p.median)); html("td", {}, r, exact(p.low)); html("td", {}, r, exact(p.high)); html("td", {}, r, p.n);
["system", "process", "clients"].forEach(function (key) { html("td", {}, r, cpuValue(p, key)); });
});
}
sel.addEventListener("change", atConn);
atConn();
// ---- full table
(function () {
var t = document.getElementById("sweep-table");
var h = html("tr", {}, html("thead", {}, t));
["series", "connections", "low", "median", "high", "observations (one per run)", "system CPU %", "process CPU %", "client CPU %"].forEach(function (c) { html("th", { scope: "col" }, h, c); });
var b = html("tbody", {}, t);
sweep.series.forEach(function (s) {
s.points.forEach(function (p, i) {
var r = html("tr", {}, b);
html("td", {}, r, familyNames[s.family] + ", " + s.label);
html("td", {}, r, conns[i]);
html("td", {}, r, exact(p.low)); html("td", {}, r, exact(p.median)); html("td", {}, r, exact(p.high));
html("td", {}, r, p.values.map(exact).join(", "));
["system", "process", "clients"].forEach(function (key) { html("td", {}, r, cpuValue(p, key)); });
});
});
})();
// ---- snapshot sets
var snaps = document.getElementById("snapshots");
["headline", "sync", "async1", "asyncn", "legacy", "kestrel", "compare", "inprocess", "wasm"].forEach(function (key) {
var st = DATA.sets[key];
if (!st) return;
var d = html("details", {}, snaps);
html("summary", {}, d, st.title);
html("p", { "class": "muted" }, d, [st.workload, st.conditions, st.policy, "source: " + st.source].join(". ") + ".");
var t = html("table", {}, d);
var h = html("tr", {}, html("thead", {}, t));
var b = html("tbody", {}, t);
if (st.x) {
[st.x.name, "published", "low", "high", "ns per request"].forEach(function (c) { html("th", { scope: "col" }, h, c); });
st.series[0].points.forEach(function (p, i) {
var r = html("tr", {}, b);
html("td", {}, r, st.x.values[i]); html("td", {}, r, p.published); html("td", {}, r, exact(p.low)); html("td", {}, r, exact(p.high)); html("td", {}, r, p.ns == null ? "" : p.ns);
});
} else if (key === "wasm") {
["path"].concat(st.modes.map(function (m) { return m.label + " (" + m.policy + ")"; })).forEach(function (c) { html("th", { scope: "col" }, h, c); });
st.groups.forEach(function (g) {
if (g.heading) html("td", { colspan: 3, style: "text-align:left;font-weight:600" }, html("tr", {}, b), g.heading);
g.series.forEach(function (id) {
var s = st.series.filter(function (x) { return x.id === id; })[0], r = html("tr", {}, b);
html("td", {}, r, s.label);
st.modes.forEach(function (m) { html("td", {}, r, s.published[m.id]); });
});
});
} else {
["row", "note", "published", "low", "high", "status"].forEach(function (c) { html("th", { scope: "col" }, h, c); });
st.groups.forEach(function (g) {
if (g.heading) html("td", { colspan: 6, style: "text-align:left;font-weight:600" }, html("tr", {}, b), g.heading);
g.series.forEach(function (id) {
var s = st.series.filter(function (x) { return x.id === id; })[0], r = html("tr", {}, b);
html("td", {}, r, s.label); html("td", { style: "text-align:left" }, r, s.note || "");
html("td", {}, r, s.published); html("td", {}, r, exact(s.low)); html("td", {}, r, exact(s.high)); html("td", {}, r, s.status);
});
});
}
});
})();
</script>
</body>
</html>