Skip to content

Build the tryEnd return array in one call - #1329

Merged
uNetworkingAB merged 1 commit into
uNetworking:masterfrom
nigrosimone:tryend-bulk-array
Sep 28, 2026
Merged

uNetworkingAB merged 1 commit into
uNetworking:masterfrom
nigrosimone:tryend-bulk-array

Conversation

@nigrosimone

Copy link
Copy Markdown
Contributor

Another small part of #1300, only the tryEnd change.

tryEnd returns [ok, done] built with Array::New(2) and two Object::Set. Each Set takes the generic property path, and when the chunk goes in the cork buffer this is more than half of the call.

The fix builds the same array with one Array::New(isolate, elements, 2).

Measured on Node 26 win-x64, 1 byte chunks inside cork, min of 7 alternating runs: from 254 ns to 100 ns per call. Same return values and same body on both builds.

@nigrosimone
nigrosimone marked this pull request as ready for review September 27, 2026 10:06
@uNetworkingAB

Copy link
Copy Markdown
Contributor

from 254 ns to 100 ns per call

Interesting. One would think they would be identical since it's all in C++ and only sets entries in an Array, but apparently not.

@uNetworkingAB

Copy link
Copy Markdown
Contributor

My thinking for the ideal architecture (as a side talk) is like this:

  1. You make 1 node::MakeCallback whenever the uWS loop has some events
  2. From that stack, you make V8 fastcalls that pull events from uWS (like progressing the event loop by calling into it)
  3. Every such progress call actually just reuses a single global ArrayBuffer that simply changes its underlying pointer
  4. Socket objects gets replaced by Integer which can be returned from a V8 fastcall: uint32 nextSocket = progressLoop();

That way, you would isolate yourself from Node.js slowdowns as much as possible - your entire bottleneck becomes the V8 fastcall that just returns a uint32 and internally replaces the ArrayBuffer backing pointer. No GC involved at all. Literally zero GC allocations.

Problem with this is, all the object-like abstractions become heavy weight (you need a way to lookup an Object from the uint32 which cannot be fastcall and kind of ruins the speedup). If you had an interface that did not treat the socket as an Object but kept it an Integer, all that loss is avoided but no one will accept such an interface and at that point you might as well swap away from Node.js.

@uNetworkingAB

Copy link
Copy Markdown
Contributor

That interface is basically the C-like interface that the very first draft of uWS used. But nobody wanted an Object-less interface, they wanted heavy weight Objects that use GC allocations and so you're cooked from the start on that one.

@uNetworkingAB
uNetworkingAB merged commit 6eb379f into uNetworking:master Sep 28, 2026
1 check failed
@nigrosimone

Copy link
Copy Markdown
Contributor Author

Thanks for merging.

The reason is in V8's api.cc: Object::Set enters V8 as an API that can run script (an indexed setter on Array.prototype would be called), so every call pays the full entry and a generic element store. Array::New(isolate, elements, length) is marked as no-script: it fills a FixedArray and wraps it, in one allocation.

The next small piece is #1330, one global handle less per request in onAborted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants