Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upnet: allow reading data into a static buffer #25436
Conversation
mscdex
added
net
performance
labels
Jan 10, 2019
This comment has been minimized.
This comment has been minimized.
nodejs-github-bot
added
C++
lib / src
labels
Jan 10, 2019
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
659c22d
to
f84b416
Jan 10, 2019
mscdex
changed the title
lib,src: allow reading data into a static buffer
net: allow reading data into a static buffer
Jan 10, 2019
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I like the feature idea. Regarding the implementation:
|
This comment has been minimized.
This comment has been minimized.
|
I'm open to suggestions (especially specific code changes), this is very much just a proof of concept to show the potential, significant performance gains. |
This comment has been minimized.
This comment has been minimized.
|
@mscdex Since you’re asking for specific code changes: addaleax@d8b783f is about what I’d have in mind (applied on top of this PR). It also brings the combined diff nicely down from +225/−66 to +139/−45. |
This comment has been minimized.
This comment has been minimized.
|
@addaleax Merged, thanks. |
This comment has been minimized.
This comment has been minimized.
|
thoughts @nodejs/collaborators ? |
This comment has been minimized.
This comment has been minimized.
|
@mscdex The code LGTM here, but I’d also like other people to weigh in more. I assume you don’t want to do unnecessary work in case this doesn’t get merged, but I also guess for a lot of people docs and tests help; so, tl;dr on the API here: This PR adds an option to Usage looks something like this: const socket = net.connect({
host: 'example.com',
port: 80,
onread: {
buffer: Buffer.allocUnsafe(65536),
callback(bytesRead, buffer) {
// Do something with the first `bytesRead` bytes of `buffer`.
// The `buffer` will always be the same object, so all data
// needs to be handled synchronously here.
}
} |
This comment has been minimized.
This comment has been minimized.
|
There has been some talk of creating a node API layer that is closer to uv, so more efficient code can be written on top of it. Is that where this is going? cf http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_start The onread use of a buffer seems to make unnecessary the uv alloc/read callback pair, that seems reasonable to me, it reduces roundtrip time in that only one C++ to js callback is needed. I guess the prices is the need for sync data handling. Maybe that's OK, as long as the data is immediately passed to a protocol parser, or unzipper, or something of the sort, though it wouldn't work for a simple pipe of data to re: lack of backpressure support, at the uv layer the caller has to explicitly start/stop data flow to exert backpressure. Isn't there a need for a readStart/Stop API as uv has? Or does the handle have that already? There is the Readable.pause() method, but I think the idea here is to bypass the streams API. |
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
18f7aca
to
86e015e
Jan 21, 2019
This comment has been minimized.
This comment has been minimized.
The |
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
86e015e
to
8cde1aa
Jan 21, 2019
This comment has been minimized.
This comment has been minimized.
|
I like the idea, we had quite a long discussion about it in denoland/deno#387 |
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
8cde1aa
to
475614d
Jan 28, 2019
This comment has been minimized.
This comment has been minimized.
|
ping @nodejs/collaborators ? |
This comment has been minimized.
This comment has been minimized.
|
@mscdex Unanswered questions in #25436 (comment) |
This comment has been minimized.
This comment has been minimized.
|
@sam-github I answered how starting and stopping is handled in this PR in #25436 (comment). Were you looking for some other information? |
This comment has been minimized.
This comment has been minimized.
|
My apologies @mscdex, that comment exactly answers my question, I don't know how I missed it. |
This comment has been minimized.
This comment has been minimized.
|
Ping @nodejs/streams to review ... in particular, @mcollina, I'd love your thoughts on this. |
|
Are you planning to add this to other areas of core? Specifically, are you planning to add it to TLS? What will happen if I think this API could be a bit limiting to what it could be achieved. What I would like to see is more a situation where the same buffer moves between the producer and the consumer, like this: let oldBuffer = null
const socket = net.connect({
host: 'example.com',
port: 80,
onread: {
buffer() {
return oldBuffer || Buffer.allocUnsafe(65536)
},
callback(bytesRead, buffer) {
destination.write(buffer, (err) => {
oldBuffer = buffer
})
}
} |
This comment has been minimized.
This comment has been minimized.
Probably.
With the PR as it currently is, node will not read from the socket.
I don't understand what exactly is expected with the suggested implementation. Is Also, due to the asynchronous nature of |
This comment has been minimized.
This comment has been minimized.
The key idea is to provide a mechanism to reuse multiple buffers, not just one. Whenever there is a need for a read, C++ can call |
This comment has been minimized.
This comment has been minimized.
That would probably cause a noticeable performance hit, unless you were willing to compromise a bit and instead have the function that the C++ side calls when the socket is read from return the next Buffer to use (by calling |
This comment has been minimized.
This comment has been minimized.
+1, that's what I meant, sorry for making it complicated. |
This comment has been minimized.
This comment has been minimized.
|
I think API should allow us to use custom memory allocators and strategies like buffer-reuse-pool. And this api shoud be compatible with |
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
3a9ed04
to
acebacc
Aug 4, 2019
This comment has been minimized.
This comment has been minimized.
|
@reklatsmasters I think a request like that is probably better implemented at a more global scale (e.g. via |
|
LGTM and nice! |
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
acebacc
to
4029b05
Aug 4, 2019
This comment has been minimized.
This comment has been minimized.
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
4029b05
to
31344e4
Aug 18, 2019
This comment has been minimized.
This comment has been minimized.
|
@mcollina I've added support for a function that can return a Buffer now, let me know if that looks good to you. |
This comment has been minimized.
This comment has been minimized.
|
@bnoordhuis let me know if these recent changes look good to you as well |
| @@ -629,6 +632,22 @@ For [IPC][] connections, available `options` are: | |||
| See [Identifying paths for IPC connections][]. If provided, the TCP-specific | |||
| options above are ignored. | |||
| For both types, available `options` include: | |||
| * `onread` {Object} - If specified, incoming data is stored in a single `buffer` | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
LGTM with an example added to the documentation |
silverwind
added
the
semver-minor
label
Aug 19, 2019
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
31344e4
to
e269428
Aug 21, 2019
This comment has been minimized.
This comment has been minimized.
mscdex
force-pushed the
mscdex:net-client-static-buffer
branch
from
e269428
to
8292b28
Aug 23, 2019
mscdex
merged commit 8292b28
into
nodejs:master
Aug 23, 2019
mscdex
deleted the
mscdex:net-client-static-buffer
branch
Aug 23, 2019
This comment has been minimized.
This comment has been minimized.
|
Thanks @mscdex this is excellent. Will this also work for constructing net servers or is it limited to net clients? |
This comment has been minimized.
This comment has been minimized.
|
@jorangreef Only clients at the moment. |
mscdex commentedJan 10, 2019
This is more or less the changes I described in nodejs/node-eps#27 with some updates since the C++ side had changed a fair amount. I thought I would put this out there to see if there was still interest in merging a feature like this (even if it's not this PR as-is).
This implementation is opt-in, only for
net(although it could obviously be adapted for other places where dynamic buffers are used), and it goes the simple and easy route and completely bypasses the data streaming feature of sockets (although'end'and other such non-data-related events are still emitted as usual).Having this kind of power is obviously not for all use cases, but for network protocol implementations it can make things a lot faster because the connection data is typically parsed synchronously and any raw data that needs to be passed on to end users could simply be copied.
Here are some example benchmark results (
recvbuflen=0indicates old/current behavior of allocating a new buffer for each chunk read):I did not add documentation or tests yet because API is not set in stone.
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passes