-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.write(Buffer) support #174
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have found why this supports only strings.
I think supporting buffers might be helpful but I don't think it's worthwhile - you might as well use Node.js core fs streams instead. I don't expect any solution would outperform them by a significant margin.
return bufs[0] | ||
} | ||
|
||
return Buffer.concat(bufs, len) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the source of the slowdown. We should not merge them, but rather keep them as a list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried having a list here, but tests that expect single flush instead of multiples break. Will work on it further now that there are 2 separate content modes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All in all Buffer.concat seem to be cheaper than multiple fs.write as long as it doesn't have to allocate memory outside of the Buffer.poolSize. Will investigate further how that could be avoided
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried fs.writev to avoid concat, but that made it slower than writeStream
benchSonicBuf*1000: 595.338ms benchSonic4kBuf*1000: 590.645ms benchCoreBuf*1000: 1.073s There is at least a 2x speed up on my system with this vs core streams when writing buffers, plus proper error handling, so might be worth it, I'll see what the perf without merging small buffers is and whether it's possible to keep the exact performance profile for utf8 |
Added
|
Master branch perf for comparison, so utf8 mode (default one) seem to be unaffected by changes
Only downside I see is that I have copied non-buffer related functions and changed logic there, so if any logic changes in the future then there are multiple places where this needs to be addressed |
@mmarchini could you take a look as well? |
Sorry, was drowned in GitHub Notifications for months. I'll take a look at it this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall looks fine, I didn't review the tests yet. Main concern as you mentioned is code duplication, so it'll be up to the project to decide if taking the burden of having some logic implemented in two places is worth it for this feature.
As for the performance aspects of this, I can see concat being faster than multiple writes, although that'll be highly dependent on system specs. Can you share specs for the machine where you ran your benchmarks?
index.js
Outdated
@@ -56,7 +57,11 @@ function openFile (file, sonic) { | |||
|
|||
// start | |||
if (!sonic._writing && sonic._len > sonic.minLength && !sonic.destroyed) { | |||
actualWrite(sonic) | |||
if (sonic.contentMode === 'buffer') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if contentMode
as an enum instead of a string would make more sense. Either way, this should be included in types/index.d.ts
and should be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great idea!
index.js
Outdated
actualWriteBuffer(sonic) | ||
} else { | ||
actualWrite(sonic) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call _actualWrite
instead of having an if
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we assign it as sonic._actualWrite
I think it's possible to do so. At the same time these are not hot paths, so not sure if it's worth it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I was thinking of using _actualWrite
for maintainability rather than performance (V8 would take care of ensuring these ifs are performant so I'm not worried about that in from a perf standpoint)
index.js
Outdated
actualWriteBuffer(this) | ||
} else { | ||
actualWrite(this) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, can't we call _actualWrite
Chip Apple M1 Max |
Updates incoming, in terms of bench Node 20 performs ~25% better, Node 18 ~ 10% node 20.3.1
node 18.16.0
|
@mmarchini please take a look if the changes are sufficient based on your comments |
@@ -106,12 +110,35 @@ function SonicBoom (opts) { | |||
this.maxLength = maxLength || 0 | |||
this.maxWrite = maxWrite || MAX_WRITE | |||
this.sync = sync || false | |||
this.writable = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot to mention, this is so that node.js stream util compose function works properly with this
wow, quite a speedup also on writing things compared to core. |
Would be interesting to bench this with io_uring support as fs operations should be much faster on linux Some weirdness is going on with win/node-14 combo during dep install |
GHA is broken on that platform, just exclude it in the workflow file. |
I'll try to take a look over the weekend :)
I have a Linux machine I can try it on. Will report back with the results |
anything I should do with this to move it forward? :) |
@mmarchini could you take a quick look? I would prefer if this did not break you. Otherwise I'm ok to ship it in the coming weeks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
mashed up quick support for .write(chunk: Buffer), works rather well as long as input is a Buffer, in case it is a utf8 string performance degradation is quite bad. See #173 for why it might be needed
if raw buffer is something that is worth exploring further then I'd be happy to put more effort into having fast paths for both types of encoding, probably by preselecting mode of operation at the time of SonicBoom instantiation