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

lib: remove queue implementation from JSStreamWrap #17918

Closed
wants to merge 4 commits into from

Conversation

addaleax
Copy link
Member

The streams implementation generally ensures that only one write()
call is active at a time. JSStreamWrap instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold isAlive() into a constant function on the native side.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

lib

@nodejs-github-bot nodejs-github-bot added the c++ Issues and PRs that require attention from people who are familiar with C++. label Dec 29, 2017
Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

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

Nice work.

this[kCurrentWriteRequest] = null;
this[kCurrentShutdownRequest] = null;

// Start reading
Copy link
Member

Choose a reason for hiding this comment

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

Dot at end. Also, somewhat superfluous comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don’t think it’s superfluous – I could imagine there’s quite a few who wouldn’t know why you’d ever want to call .read(0).


this.stream.cork();
for (var n = 0; n < bufs.length; n++)
this.stream.write(bufs[n], done);
for (var i = 0; i < bufs.length; i++)
Copy link
Member

Choose a reason for hiding this comment

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

If you're making stylistic changes anyway: let i = ... (or for (const buf of bufs))

Copy link
Member Author

Choose a reason for hiding this comment

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

We are not allowed to use let in loops, the linter yells about that. :(

for (const buf of bufs) seems to work though!


var pending = bufs.length;
const handle = this._handle;
Copy link
Member

Choose a reason for hiding this comment

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

Unused?

Copy link
Member Author

Choose a reason for hiding this comment

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

The linter would complain about that. :)

Copy link
Member

Choose a reason for hiding this comment

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

I'm not seeing it, though. Same for the const handle = ... on line 112.

Copy link
Member Author

Choose a reason for hiding this comment

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

It’s passed to the self.finishWrite() call down on line 162

Copy link
Member

Choose a reason for hiding this comment

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

Oh, indeed. Unfortunate fold in the diff.

handle.onwrite = (req, bufs) => this.doWrite(req, bufs);
// Inside of the following functions, `this` refers to the handle
// and `this.owner` refers to this JSStreamWrap instance.
handle.isClosing = function() { return this.owner.isClosing(); };
Copy link
Member

Choose a reason for hiding this comment

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

would be slightly better to avoid the closures entirely and just make these top level functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jasnell done!

@addaleax
Copy link
Member Author

addaleax commented Jan 2, 2018

@addaleax addaleax added lib / src Issues and PRs related to general changes in the lib or src directory. author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Jan 2, 2018

var pending = bufs.length;
const handle = this._handle;
const self = this;

Choose a reason for hiding this comment

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

Is it still needed ?

Copy link
Member Author

Choose a reason for hiding this comment

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

@billouboq it’s used inside the done() function below :)


this.stream.cork();
for (var n = 0; n < bufs.length; n++)
this.stream.write(bufs[n], done);
for (const buf of bufs)
Copy link
Member

Choose a reason for hiding this comment

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

can you use a for(;;) loop here? for-of is still extremely slower than for(;;).


handle.finishWrite(req, errCode);
setImmediate(() => {
self.finishWrite(handle, errCode);
Copy link
Member

Choose a reason for hiding this comment

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

you can avoid this closure completely with setImmediate(finishWrite, self, handle, errCode).


this.finishWrite(handle, uv.UV_ECANCELED);
this.finishShutdown(handle, uv.UV_ECANCELED);

Copy link
Member

Choose a reason for hiding this comment

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

can you remove this closure as well?

@addaleax addaleax removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Jan 3, 2018
@addaleax
Copy link
Member Author

addaleax commented Jan 3, 2018

@mcollina I’ll get to addressing your comments later, but I’ll point out that at least in the case of the for loop that was a suggestion by an earlier comment (I can switch back tho) and that this is the slow path already, so squeezing the last bits of performance out of it might not be that important

The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.
@addaleax
Copy link
Member Author

addaleax commented Jan 4, 2018

@mcollina I’ve addressed the for (const … of …) suggestion by you.

I also tried to benchmark all of the suggested changes via the benchmark added in #17983, but did not get a statistically significant improvement as a result, so I’d prefer to stick to the more readable code here.

@mcollina
Copy link
Member

mcollina commented Jan 5, 2018

I'm 👍 if you want to keep if it does not regress anything. I prefer to not use code that might become problematic just for stylistic reasons (and for (;;) is beautiful).

Have you thought about refactoring all those closures as well? I don't expect to get any perf boost, but it might help memory consumption and the gc in the long run. Feel free to ignore those, it's just a nit.

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

@addaleax
Copy link
Member Author

addaleax commented Jan 5, 2018

@mcollina I benchmarked all of your suggestions together (via that linked benchmark). I hadn’t really thought about gc, thanks for the reminder! What would be the best way to measure the impact of a change like that?

@bnoordhuis
Copy link
Member

@addaleax Slight tangent but an idea I've had is roughly to:

  1. Instrument with v8::Isolate::AddGCEpilogueCallback()
  2. Log memory usage reported by v8::Isolate::GetHeapSpaceStatistics() after every GC
  3. Collate the data after the process exits

With the idea that you compare runs of the benchmarks or the test suite to see if there are large swings in GC frequency or heap usage.

@mcollina
Copy link
Member

mcollina commented Jan 5, 2018

I used --trace-gc-object-stats in the past with great success.

@joyeecheung
Copy link
Member

@bnoordhuis I think what you've descried is basically what --trace_gc --trace_gc_verbose does?

@bnoordhuis
Copy link
Member

By 'log' I mean 'write to file' in a format that's machine readable.

--trace_gc dumps everything to stdout interleaved with normal output. Okay for eyeballing stuff, not so great for automation.

@addaleax
Copy link
Member Author

addaleax commented Jan 7, 2018

Landed in b171adc

I’ll take a look at the closures at another point, this PR isn’t introducing new ones and I’d like to get around to addressing the issue from #17938 (comment)

@addaleax addaleax closed this Jan 7, 2018
@addaleax addaleax deleted the wrap-js-stream-simplify branch January 7, 2018 20:32
addaleax added a commit that referenced this pull request Jan 7, 2018
The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.

PR-URL: #17918
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.

PR-URL: #17918
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.

PR-URL: #17918
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
@MylesBorins
Copy link
Contributor

This is currently breaking some stuff on v9.x

It is possible that after #18050 this will work

@addaleax
Copy link
Member Author

@MylesBorins It applies and tests cleanly on my machine … could you show the error you were seeing?

@MylesBorins
Copy link
Contributor

@addaleax I'm assuming it was unblocked by the http2 update. Will look at this again in a bit

evanlucas pushed a commit that referenced this pull request Jan 22, 2018
The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.

PR-URL: #17918
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
evanlucas pushed a commit that referenced this pull request Jan 30, 2018
The streams implementation generally ensures that only one write()
call is active at a time. `JSStreamWrap` instances still kept
queue of write reqeuests in spite of that; refactor it away.

Also, fold `isAlive()` into a constant function on the native side.

PR-URL: #17918
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory.
Projects
None yet
Development

Successfully merging this pull request may close these issues.