node icon indicating copy to clipboard operation
node copied to clipboard

`Writable.write(buf, sourceOffset, sourceLength)`

Open ronag opened this issue 1 year ago โ€ข 1 comments

I want to add a Writable.write(sourceBuf, sourceOffset, sourceLength) API to Writable to get a bit of extra performance in the following case:

writable.write(buf.subarray(offset, length))

The subarray call (particularly regarding allocating Buffer) can be quite slow and avoided in this API. Hence I would like to be able to do something like:

writable.write(buf, offset, length)

I'm not sure how the API should look, though, or whether adding another signature to write would break anyone.

Internally stream implementation would need to explicitly support this by implementing something like _writeSlice and _writevSlice

ronag avatar Dec 02 '24 07:12 ronag

@nodejs/streams @mcollina

ronag avatar Dec 02 '24 07:12 ronag

I think the complexity budget is already over what we could afford in those methods, and I'm afraid adding this will slow everything down or make it even worse.

Why don't you explore doing this in OSS instead?

mcollina avatar Dec 07 '24 19:12 mcollina

OSS?

ronag avatar Dec 07 '24 19:12 ronag

Sorry, as a separate Open Source module.

mcollina avatar Dec 08 '24 08:12 mcollina

I think this would need support in node core to be meaningful. The conversion into buffer happens in native code for many APIs.

ronag avatar Dec 08 '24 11:12 ronag

I'm not convinced that allocating an object is the biggest cost here. It adds some overhead, but is it the biggest source of overhead to tackle? I'm afraid it would cost us massive refactors with little gains, but I'd be happy to be proven wrong.

mcollina avatar Dec 12 '24 08:12 mcollina

import { bench, run } from 'mitata'
import { Writable } from 'node:stream'

const w = new Writable({
  write (chunk, encoding, callback) {
    callback()
  }
})
const buf = Buffer.allocUnsafe(1024)

bench('writable w/ subarray', () => {
	w.write(buf.subarray(0, 1023))
})

bench('writable w/o subarray', () => {
	w.write(buf)
})

await run()

benchmark                   avg (min โ€ฆ max) p75   p99    (min โ€ฆ top 1%)
------------------------------------------- -------------------------------
writable w/ subarray          58.64 ns/iter  50.93 ns โ–ˆ                    
                       (43.96 ns โ€ฆ 2.15 ยตs) 183.88 ns โ–ˆโ–†โ–‚โ–โ–‚โ–‚โ–‚โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–
writable w/o subarray         29.68 ns/iter  21.47 ns โ–ˆ                    
                       (19.82 ns โ€ฆ 3.63 ยตs) 132.98 ns โ–ˆโ–‚โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–

Buffer object allocation is comically slow. Relatively speaking. Though I'm not sure 20ns is much in context of a real application.

Writing 1G of data it adds an overhead of 1e12 / 16e3 * 20 / 10e9 = 0,125s

Assuming a bandwidth of 1GB/s w/o subarray, that means that we would get 0,88GB/s w/ subarray... so it's not negligable

ronag avatar Dec 12 '24 09:12 ronag

That's very odd, I didn't expect .subarray() to be so slow. It's not doing any copying or anything similar... therefore our FastBuffer does not seem that fast.

I can see why you'd want to do this, but it's a humungous change in the internals of streams, C++ streams, etc, and the majority of the ecosystem is not going to benefit from it, as it's a minor use case.

Maybe it could be investigated if .subarray() or Uint8Array initialization could be made faster.

mcollina avatar Dec 13 '24 08:12 mcollina

Maybe it could be investigated if .subarray() or Uint8Array initialization could be made faster.

I have looked into it and it's in V8. Nothing we can do as far I can tell.

ronag avatar Dec 13 '24 11:12 ronag

I'm not sure this is solvable then, unless there is a significant sponsor for redoing quite a large portion of the internal streams API. On top, it would not benefit the "base" case, making it even harder to "sell".

mcollina avatar Dec 23 '24 08:12 mcollina

I will have a go at it if I ever have time.

ronag avatar Dec 23 '24 09:12 ronag

There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the https://github.com/nodejs/node/labels/never-stale label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document.

github-actions[bot] avatar Jun 22 '25 01:06 github-actions[bot]

There has been no activity on this feature request and it is being closed. If you feel closing this issue is not the right thing to do, please leave a comment.

For more information on how the project manages feature requests, please consult the feature request management document.

github-actions[bot] avatar Jul 22 '25 01:07 github-actions[bot]