-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
feat(stream): make Readable.from performance better #37609
Conversation
wwwzbwcom
commented
Mar 5, 2021
•
edited
Loading
edited
- prevent unnecessary await
- convert recursive to loop
Thanks, I'm not sure I entirely understand this change and the for loop - will comment inline also cc @nodejs/streams |
lib/internal/streams/from.js
Outdated
if (isAysnc) { | ||
r = await iterator.next(); | ||
} else { | ||
r = iterator.next(); |
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.
The change here is that iterator.next()
with a sync iterator is not await
ed and if there are multiple next
calls it is pushed directly without waiting for the next next
?
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 the iterator is not async, it's not neccessary to await it
Any benchmark? |
I think it would be more readable to split this into two separate methods, i.e. |
@ronag pun intended 😅 ? |
Thanks for so much help. |
Here is a simple benchmark shows that prevent unecessary await can bring more than 5x faster performance: Code: 'use strict';
async function bench() {
console.time('await');
for (let i = 0; i < 1e8; i++) {
let a = await i;
}
console.timeEnd('await');
console.time('no await');
for (let i = 0; i < 1e8; i++) {
let a = i;
}
console.timeEnd('no await');
console.time('check await');
for (let i = 0; i < 1e8; i++) {
if (i === Promise.resolve(i)) {
let a = await i;
} else {
let a = i;
}
}
console.timeEnd('check await');
}
bench(); Result: await: 6.409s
no await: 77.315ms
check await: 1.266s Also, loop always bring better time and space performance compares to recursive. |
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 if @benjamingr also approves.
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 am generally fine with this - thanks :)
- The PromiseResolve probably bit needs to be amended to work with third-party thenables
- Since this is a performance PR - I want to see a benchmark so we know this actually improves performance.
- This definitely needs a CITGM run IMO since it changes the timings of
Readable.from
in non-trivial ways.
Once there is a benchmark (let me know) I'll start a CITGM run, if you want help with setting the benchmark up - feel free to ask any questions or ask for assistance here :)
@benjamingr Thanks for your advice I think I know how to write a benchmark, but dont know how to let the benchmark compare between current and the earlier version of code. Should I benchmark earlier version manually and post the result, or there is a better way? |
What does |
There's a guide on how to create a benchmark here: How to compare different node versions here: There are also some |
await implicitly calls |
Co-authored-by: Robert Nagy <ronagy@icloud.com>
@benjamingr Hello, I add the benchmark, looks like it has a 30% improvement here's the result: # Run bechmark
node benchmark/compare.js --old ./node-master --new ./node-dev --filter "readable-from" streams > compare-pr-dev.csv
cat compare-dev.csv | Rscript benchmark/compare.R
# Result
Warning message:
In as.POSIXlt.POSIXct(Sys.time()) :
unknown timezone 'default/Asia/Shanghai'
confidence improvement accuracy (*) (**) (***)
streams/readable-from.jsn=10000000 *** 30.15 % ±4.32% ±5.76% ±7.52%
Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 1 comparisons, you can thus
expect the following amount of false-positive results:
0.05 false positives, when considering a 5% risk acceptance (*, **, ***),
0.01 false positives, when considering a 1% risk acceptance (**, ***),
0.00 false positives, when considering a 0.1% risk acceptance (***) |
One last comment |
@benjamingr Where is the comment, I can't find 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.
lgtm
@benjamingr check for thenable has fixed ;) |
I cant find the relation between failing checks and my changes, can anyone help me figure out? Thanks :) |
@wwwzbwcom it looks like CITGM pipeline were red for months already, might be nothing to worry about 😬 |
Thank you for your contribution and patience. Let's hope this doesn't break too much :) Please note the naming convention for the future :) (Which is subsystem: change and not semantic commits) Landed in 2c251ff 🎉 |
PR-URL: #37609 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
PR-URL: #37609 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
bench.start(); | ||
s.on('data', (data) => { | ||
// eslint-disable-next-line no-unused-expressions | ||
data; |
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.
Why "data" is here?