Skip to content
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

Closed
wants to merge 11 commits into from
Closed

feat(stream): make Readable.from performance better #37609

wants to merge 11 commits into from

Conversation

wwwzbwcom
Copy link
Contributor

@wwwzbwcom wwwzbwcom commented Mar 5, 2021

  • prevent unnecessary await
  • convert recursive to loop

@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Mar 5, 2021
@benjamingr
Copy link
Member

Thanks, I'm not sure I entirely understand this change and the for loop - will comment inline also cc @nodejs/streams

@benjamingr benjamingr added the stream Issues and PRs related to the stream subsystem. label Mar 5, 2021
if (isAysnc) {
r = await iterator.next();
} else {
r = iterator.next();
Copy link
Member

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 awaited and if there are multiple next calls it is pushed directly without waiting for the next next?

Copy link
Contributor Author

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

lib/internal/streams/from.js Outdated Show resolved Hide resolved
lib/internal/streams/from.js Outdated Show resolved Hide resolved
lib/internal/streams/from.js Outdated Show resolved Hide resolved
@ronag
Copy link
Member

ronag commented Mar 5, 2021

Any benchmark?

@ronag
Copy link
Member

ronag commented Mar 5, 2021

I think it would be more readable to split this into two separate methods, i.e. syncNext and asyncNext.

@benjamingr
Copy link
Member

@ronag pun intended 😅 ?

lib/internal/streams/from.js Outdated Show resolved Hide resolved
@wwwzbwcom
Copy link
Contributor Author

Thanks for so much help.
Just create draft PR to save my work earlier, haven't test and check carefully yet, sorry for that

@wwwzbwcom
Copy link
Contributor Author

Any benchmark?

@ronag

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.

@wwwzbwcom wwwzbwcom requested review from ronag and benjamingr March 8, 2021 05:15
@wwwzbwcom wwwzbwcom changed the title feat: make Readable.from performance better feat(stream): make Readable.from performance better Mar 8, 2021
@wwwzbwcom wwwzbwcom marked this pull request as ready for review March 8, 2021 05:21
lib/internal/streams/from.js Outdated Show resolved Hide resolved
lib/internal/streams/from.js Outdated Show resolved Hide resolved
Copy link
Member

@ronag ronag left a 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.

lib/internal/streams/from.js Outdated Show resolved Hide resolved
Copy link
Member

@benjamingr benjamingr left a 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 :)

@wwwzbwcom
Copy link
Contributor Author

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?

@ronag
Copy link
Member

ronag commented Mar 8, 2021

The PromiseResolve probably bit needs to be amended to work with third-party thenables

What does await do in this case? I think there should be a spec for this somewhere? Do we have/need a util.isPromise/util.isThenable helper?

@Linkgoron
Copy link
Member

Linkgoron commented Mar 8, 2021

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?

There's a guide on how to create a benchmark here:
https://github.com/nodejs/node/blob/master/doc/guides/writing-and-running-benchmarks.md#basics-of-a-benchmark

How to compare different node versions here:
https://github.com/nodejs/node/blob/master/doc/guides/writing-and-running-benchmarks.md#comparing-nodejs-versions

There are also some Readable benchmarks in benchmark/streams which might be relevant

@benjamingr
Copy link
Member

@ronag

What does await do in this case? I think there should be a spec for this somewhere? Do we have/need a util.isPromise/util.isThenable helper?

await implicitly calls Promise.resolve in this case (with the native promise resolve), but the explicit goal of this PR is to optimize that await away in certain cases. While usually I am not a fan of a util.isThenable here it seems appropriate.

@wwwzbwcom
Copy link
Contributor Author

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 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 (***)

@wwwzbwcom wwwzbwcom requested a review from benjamingr March 12, 2021 18:12
@benjamingr
Copy link
Member

One last comment

@wwwzbwcom
Copy link
Contributor Author

One last comment

@benjamingr Where is the comment, I can't find it

@wwwzbwcom wwwzbwcom requested a review from ronag March 17, 2021 02:44
@bricss
Copy link

bricss commented Mar 17, 2021

@wwwzbwcom here #37609 (comment)

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

benchmark/streams/readable-from.js Outdated Show resolved Hide resolved
@wwwzbwcom
Copy link
Contributor Author

@benjamingr check for thenable has fixed ;)

@nodejs-github-bot
Copy link
Collaborator

@benjamingr
Copy link
Member

@wwwzbwcom
Copy link
Contributor Author

CITGM: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/2647/

I cant find the relation between failing checks and my changes, can anyone help me figure out? Thanks :)

@nodejs-github-bot
Copy link
Collaborator

@bricss
Copy link

bricss commented Mar 21, 2021

@wwwzbwcom it looks like CITGM pipeline were red for months already, might be nothing to worry about 😬

@benjamingr benjamingr added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Mar 22, 2021
@benjamingr
Copy link
Member

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 🎉

@benjamingr benjamingr closed this Mar 22, 2021
benjamingr pushed a commit that referenced this pull request Mar 22, 2021
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>
ruyadorno pushed a commit that referenced this pull request Mar 24, 2021
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>
@ruyadorno ruyadorno mentioned this pull request Mar 30, 2021
bench.start();
s.on('data', (data) => {
// eslint-disable-next-line no-unused-expressions
data;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "data" is here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. needs-ci PRs that need a full CI run. stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants