Conversation
0b56e6f to
b4a8dbb
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66358 +/- ##
=======================================
Coverage 90.36% 90.36%
=======================================
Files 792 792
Lines 275386 275462 +76
Branches 52775 52791 +16
=======================================
+ Hits 248843 248918 +75
+ Misses 16979 16953 -26
- Partials 9564 9591 +27
🚀 New features to boost your workflow:
|
H4ad
left a comment
There was a problem hiding this comment.
I only found one benchmark that covers zstd (https://github.com/Cherry/node/blob/b4a8dbbc5a6194bd36229ffaa3bf83dcdf14f2e0/benchmark/zlib/pipe.js), might be insteresting to add another one to cover this case (at least level 12, which is fast enough to not be too long for a benchmark)
|
Added the benchmark - here's some results on my local machine for reference:
|
This comment was marked as outdated.
This comment was marked as outdated.
| if (typeof buffer === 'string') { | ||
| // The stream encodes strings with defaultEncoding, so only a UTF-8 length | ||
| // is known to match what gets written. | ||
| if (opts?.defaultEncoding !== undefined) { |
There was a problem hiding this comment.
nit: you could check for utf-8 and utf8 as well here.
And I wonder if the utf8 is really the only one, wonder if latin1/ascii and others might be applied here, except for hex/base64/base64url as denoted by
Lines 990 to 993 in 75e4bbe
There was a problem hiding this comment.
Good point on utf8/utf-8. I was a bit wary of a list, though. A wrong pledge isn't just slower, zstd errors out with "Src size is incorrect". Buffer.byteLength can overcount malformed hex/base64 ('ab cd' in hex gives 2 when the actual length is 1), so we'd have to get the list exactly right.
Another option: convert the string up front with Buffer.from(buffer, opts.defaultEncoding) and pledge its real length. The stream does that conversion anyway since decodeStrings is on, so I don't think it should cost anything (thought not sure?), and it'd work for every encoding. The downside is a slightly bigger diff, since the hook would need to hand back the buffer too.
Happy to go either way. What do you think?
There was a problem hiding this comment.
Let's handle only check for utf8/utf-u and then we can have a follow-up PR with a extended benchmark for other encodings and we can try handle more encodings by:
- add to the bypass if the encoding is safe 1:1
- or try approach of using Buffer., in this case, we will be able to measure the performance diff between current approach and the new one
The PR is good enough now, so I would avoid over-doing it for now and have a follow-up for it to ensure correctness
There was a problem hiding this comment.
Thanks, makes sense, done. utf8/utf-8 now get the pledge too. Other encodings are left for a follow-up with a benchmark, as you suggested - I think there's probably some other low hanging fruit here given there's still pretty large differences in the async and sync implementations.
|
Benchmark GHA (zlib / zstd-): https://github.com/nodejs/node/actions/runs/36421711232 Results
Benchmark results:
|
zstdCompress() writes its input and ends the frame in separate calls, so zstd cannot infer the input size the way it does for zstdCompressSync(), and sizes its tables for an unbounded stream. Default pledgedSrcSize to the input's byte length. The output is now identical to zstdCompressSync() and several times faster at higher levels. An explicit pledgedSrcSize, or a string with a custom defaultEncoding, keeps the current behavior. Signed-off-by: James Ross <james@jross.me>
Signed-off-by: James Ross <james@jross.me>
4b9e7ea to
b22c547
Compare
We precompress our site's static assets at build time, and compression-webpack-plugin calls
zlib.zstdCompress()once per file. On the same files, that was 3-5x slower thanzstdCompressSync().zstdCompressSync()hands zstd the whole input in oneZSTD_e_endcall, so zstd infers the size.zstdCompress()writes the input and ends the frame in separate calls, so zstd never learns the size and sizes its tables for an unbounded stream.This defaults
pledgedSrcSizeto the input's byte length inzstdCompress(). Async output is now byte-identical tozstdCompressSync():That's 162 JS/CSS/SVG files from our production build (up to ~370 KB each), on Linux x64. Windows shows the same pattern. Single large inputs (1-64 MB) are unchanged apart from the few header bytes that record the size.
An explicit
pledgedSrcSizeis used as-is. Strings with a customdefaultEncodingaren't pledged, since their byte length depends on that encoding.