Repository navigation
POC: collectJson, the body parsed by JSON::Parse, to a callback or a promise - #1348
nigrosimone wants to merge 1 commit into
Conversation
Benchmark:
|
| Scenario | Base req/s | Head req/s | Head / base | Rounds | Noise | Busy | CPU per request |
|---|---|---|---|---|---|---|---|
| http/hello-world | 202087 | 202541 | 0.998x (-0.2%) | 0.97 to 1.03 | ±4.1% | 100% / 100% | 4.95 / 4.93 us (1.002x) |
| http/headers | 173237 | 172926 | 1.004x (+0.4%) | 0.95 to 1.02 | ±4.2% | 100% / 100% | 5.77 / 5.78 us (0.996x) |
| http/json-post | 155200 | 156551 | 1.006x (+0.6%) | 0.99 to 1.03 | ±4.6% | 100% / 100% | 6.44 / 6.38 us (0.993x) |
| http/cached | 232841 | 229850 | 0.996x (-0.4%) | 0.96 to 1.01 | ±2.3% | 100% / 100% | 4.29 / 4.35 us (1.004x) |
| ws/echo-20b | 210130 | 207745 | 0.991x (-0.9%) | 0.96 to 1.00 | ±4.3% | 100% / 100% | 4.76 / 4.81 us (1.009x) |
| ws/echo-4kb | 183087 | 180531 | 0.987x (-1.3%) | 0.97 to 1.00 | ±3.5% | 100% / 100% | 5.46 / 5.54 us (1.013x) |
4 rounds of 3s per scenario, each round loading base, head and a second process of each one, from a copy of its build, after the other, in an order that changes every round. "Head / base" is the median of the per-round ratios of req/s and "Rounds" their range. "Noise" is how far base/base and head/head, the same code on both sides, got from 1 in this same run: that is what the machine did, so a row is marked only when the median moved further than that, at least 2%, and every round moved the same way: 👀 slower, 🏆 faster. "Busy" is the cpu time of the server process over the round and "CPU per request" that time divided by the requests it answered, with the head/base ratio judged against its own same-code band and bold when it cleared it. When busy is well under 100% the load generator set the pace, the req/s are its and the cpu per request is the column to read. Only the ratios are comparable across runs, the absolute figures depend on the runner.
Node v26.10.0, Intel(R) Xeon(R) 6973P-C, 4 cores (server on cpu 0, load on 2,3), http_load_test and load_test with 100 connections.
POC, not to merge: a draft to see the cost and the impact on the existing code of the collectJson you suggested in #1347, and if this way is worth it.
collectJson(maxSize, cb)collects the body like collectBody and parses it in C++ with JSON::Parse. cb gets the value, or undefined when the body is over maxSize or not valid JSON.collectJson(maxSize)returns a Promise. 413 and 400 come from uWS, on abort it never settles, and if there is no onAborted it attaches one that only marks res invalid. It resolves in a node::CallbackScope, so the code after the await is still corked:const user = await res.collectJson(1024 * 1024); res.end(JSON.stringify(user));needs nothing else.Impact: one new function in HttpResponseWrapper.h (about 100 lines) and the types, nothing existing changes. Tested locally on Node 22, 24 and 26, and with ASAN+UBSan: both forms, 600 KB in 40 pieces, utf-8, over maxSize, invalid, empty body, abort.
CPU per request against onData + Buffer.concat (the json-post scenario), WSL, server on one core, autocannon -c 100, 8 to 10 rounds. Two identical callback arms differ by 1 to 4%, that is the noise.
So it is faster from about 1 KB, the same on big bodies where parsing is all the time, and about 5% slower on a 10 B body, which I don't understand yet. The promise costs a few % over the callback.