| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
I've added a benchmark to check performance and it shows that this is as performant as the current implementation. However, the benchmark could definitely use a review as it's very easy to do benchmarks wrong and I have no particular expertise. @nodejs/benchmarking |
Sorry, something went wrong.
Sorry, something went wrong.
There was a problem hiding this comment.
You can use '.'.repeat(len) for this kind of thing nowadays.
Sorry, something went wrong.
There was a problem hiding this comment.
But also run msg.test(/./) to flatten the string. Creates more reliable benchmark results.
Sorry, something went wrong.
There was a problem hiding this comment.
@mscdex @trevnorris So I should do this?
const msg = `"${'.'.repeat(len)}"`;
msg.match(/./);
Sorry, something went wrong.
There was a problem hiding this comment.
sure. that looks good. i'm not sure what the internal mechanics of repeat() do, but if it's similar to doing += then should take care of it.
Sorry, something went wrong.
There was a problem hiding this comment.
OK, done! The benchmark shows frighteningly similar results for Node.js 6.2.0 vs. this PR.
6.2.0:
$ node benchmark/child_process/child-process-exec-stdout.js child_process/child-process-exec-stdout.js len=64 dur=5: 40873.45059 child_process/child-process-exec-stdout.js len=256 dur=5: 40853.93567 child_process/child-process-exec-stdout.js len=1024 dur=5: 40878.88575 child_process/child-process-exec-stdout.js len=4096 dur=5: 40851.36544 child_process/child-process-exec-stdout.js len=32768 dur=5: 40876.38241 $
This PR:
$ ./node benchmark/child_process/child-process-exec-stdout.js child_process/child-process-exec-stdout.js len=64 dur=5: 40873.08798 child_process/child-process-exec-stdout.js len=256 dur=5: 40860.52815 child_process/child-process-exec-stdout.js len=1024 dur=5: 40878.13205 child_process/child-process-exec-stdout.js len=4096 dur=5: 40869.97254 child_process/child-process-exec-stdout.js len=32768 dur=5: 40863.11006 $
Sorry, something went wrong.
|
I did some benchmarking outside of node recently that was related to this and IIRC concatenating strings was faster (rather than creating the string at the end)? This would need some double checking though. |
Sorry, something went wrong.
There was a problem hiding this comment.
Couldn't this yield incorrect results if a variable-width encoded character is split across two chunks?
Sorry, something went wrong.
There was a problem hiding this comment.
Yes, this is true. That's why setEncoding() was being used initially. This will need to be solved differently I think.
Sorry, something went wrong.
There was a problem hiding this comment.
I think the solution is to always store the data as Buffers and concat/toString them just before being emitted.
Sorry, something went wrong.
There was a problem hiding this comment.
isn't that what StringDecoder is for? (didn't look at this in detail so may be missing something)
Sorry, something went wrong.
There was a problem hiding this comment.
One alternative would be to keep setEncoding(), etc. and only change the length increment lines to something like:
stderrLen += (this.encoding ? Buffer.byteLength(chunk, this.encoding) : chunk.byteLength);I haven't benchmarked the difference between that solution and only converting to string at the end though.
EDIT: or another take on this alternative solution would be to add another data handler first that depends on whether encoding is set that increments the length appropriately, if you want to avoid doing the encoding check on every data event (maybe the performance difference is negligble, I haven't tested):
var onStderrData;
if (encoding) {
child.stderr.setEncoding(encoding);
onStderrData = function(chunk) {
stderrLen += Buffer.byteLength(chunk, this.encoding);
};
} else {
onStderrData = function(chunk) {
stderrLen += chunk.byteLength;
};
}
child.stderr.addListener('data', onStderrData);
child.stderr.addListener('data', function(chunk) {
if (stderrLen > options.maxBuffer) {
// ...
});EDIT 2: Actually the above solution wouldn't work since the encoding could be changed at any time, so you would need to do what I originally suggested (checking this.encoding on each chunk).
Sorry, something went wrong.
There was a problem hiding this comment.
@jasnell The problem this PR is trying to solve is that the units for maxBuffer are in bytes, but once you set up a string decoder, there is no way to know how many bytes went into making the string that gets emitted on data. That means you can no longer rely on chunk.length since you could have multi-byte characters.
Sorry, something went wrong.
There was a problem hiding this comment.
@mscdex ... +1 gotcha.
Sorry, something went wrong.
There was a problem hiding this comment.
On second thought, does it make sense to emit on byte count and not character count if encoding is set? Only because the string passed to 'data' can vary noticeably if reading in utf8 characters.
Sorry, something went wrong.
|
By the way, the discussion in #1902 is probably relevant. The PR changed over time and some of the commits seem to be lost, but check out the comments by @piscisaureus. |
Sorry, something went wrong.
|
@cjihrig I personally don't see a problem with going over by at most 3 bytes if the stream ends on a partial character, but you could always peek into the StringDecoder state to see how many bytes are buffered at the end of the stream to account for those bytes. |
Sorry, something went wrong.
|
@mscdex While I'm not sure whether we can account for this, there are cases where the buffered character is larger. Here's an exaggerated case: const s = 'ก็็็็็็็็็็็็็็็็็็็็';
console.log(s.length); // 21
console.log(Buffer.from(s)); // 63 |
Sorry, something went wrong.
|
@trevnorris and Buffer.byteLength(s, 'utf8') === 63, so I still don't see what the problem is with the presented alternative solutions (mine or yours). If you pass say Buffer.from(s).slice(0, 3) to a string decoder, a single character will be returned. Calling Buffer.byteLength() on that character will return 3 bytes. Concatenating the output from the string deocder still results in the same s value. |
Sorry, something went wrong.
|
@mscdex Sorry, didn't convey my point. It's that while the character code may be complete it's also possible that the rendered character is incomplete because of missing diacritical marks. But also that this isn't a case we're able to handle. |
Sorry, something went wrong.
|
For example: Buffer('Á'); // <Buffer c3 81>
Buffer('Á'); // <Buffer 41 cc 81>Appear to be the same character, but the first is actually '\u00c1' and the other is 'A\u0301'. So the rendered output on the other end is incomplete. |
Sorry, something went wrong.
|
@trevnorris I still don't quite follow. Are you referring to a situation where Buffer.from([0x41,0xcc]) is passed to the string decoder but the needed 0x81 is never received from the actual stderr stream? If so, there is nothing that node or anyone else can ever do about that, but what matters is that we can count the bytes received. So in that particular case we'd see the 'A' emitted by the decoder which would count for one byte and at stderr end we can stderrLen += child.stderr._readableState.decoder.charReceived to account for the received 0xcc byte. Now that I think about it, maybe yet another (better) solution is to just manually instantiate a string decoder and pass that the data chunks, that way we avoid having to peer into _readableState and the string decoder's state variables? |
Sorry, something went wrong.
That's where I was leading with #6764 (comment) |
Sorry, something went wrong.
|
@jasnell Ah ok. The only difference performance-wise with that last proposed solution is that it may be still be possible for streams to internally concatenate buffered Buffers (with Buffer.concat()) vs concatenating plain strings (when using .setEncoding()) before data is emitted? I'm not sure what that cost difference is. |
Sorry, something went wrong.
|
Yeah, not sure either. Would need to benchmark it to be certain
|
Sorry, something went wrong.
|
@mscdex This isn't about the string decoder. It's about the (very unlikely) possibility of data coming in, then say be logged. Where it would basically be: console.log('A');
console.log('\u0301');The rendered output would be wrong, even though each character is technically correct. I'm not saying we should consider supporting this properly. Just wanted to make the point that there are cases where even having the full utf8 characters won't render properly. |
Sorry, something went wrong.
|
If someone was truly worried about that case they could buffer and wait for a new line to be received before calling console.log(). Not all that worried about that possibility |
Sorry, something went wrong.
|
@trevnorris That's a separate issue though and node can't do anything about that anyway. Being able to count the bytes is the issue here, but even that separate issue isn't an issue for the majority of people that use exec() with a callback since the entire (buffered) string would be available for each stream (so it's not possible for users to console.log() multiple times unless they explicitly slice the string or also add their own stream event handlers, but that is a user problem at those points). |
Sorry, something went wrong.
|
@mscdex sure thing. there was another comment that prompted me to make mention of this, and clarify that node doesn't have any intention of handling it. |
Sorry, something went wrong.
|
I've changed the implementation to only run toString() on the buffer in the exit handler. My interpretation of the discussion above is that this still doesn't account for some unlikely edge cases, but that we probably can't do much about those anyway. PTAL. |
Sorry, something went wrong.
|
@Trott You'd need to do it in the close event since data can still be received after exit. Nevermind, I see that exithandler is actually used for the close event. |
Sorry, something went wrong.
|
CI seems to be experiencing less heartburn now than 2 days ago. Let's try again. CI: https://ci.nodejs.org/job/node-test-pull-request/2754/ |
Sorry, something went wrong.
|
|
||
| child.stdout.addListener('data', function(chunk) { | ||
| stdoutLen += chunk.length; | ||
| // If `child.stdout.setEncoding('utf8')` happened in userland, convert |
There was a problem hiding this comment.
Do you mean any setEncoding() call, or specifically w/ 'utf8' passed?
Sorry, something went wrong.
There was a problem hiding this comment.
I mean any setEncoding(). Will change the comment...
Sorry, something went wrong.
|
OK... CI looks good... https://ci.nodejs.org/job/node-test-pull-request/2754/ Benchmarks are alarmingly on par: $ ./node-pr-6764 benchmark/child_process/child-process-exec-stdout.js child_process/child-process-exec-stdout.js len=64 dur=5: 40871.77387 child_process/child-process-exec-stdout.js len=256 dur=5: 40870.66593 child_process/child-process-exec-stdout.js len=1024 dur=5: 40905.69407 child_process/child-process-exec-stdout.js len=4096 dur=5: 40880.96963 child_process/child-process-exec-stdout.js len=32768 dur=5: 40856.23579 $ node-6.2.0 benchmark/child_process/child-process-exec-stdout.js child_process/child-process-exec-stdout.js len=64 dur=5: 40872.87520 child_process/child-process-exec-stdout.js len=256 dur=5: 40868.80411 child_process/child-process-exec-stdout.js len=1024 dur=5: 40878.62745 child_process/child-process-exec-stdout.js len=4096 dur=5: 40876.45268 child_process/child-process-exec-stdout.js len=32768 dur=5: 40866.85730 $ Do we feel good about this as a solution to the problem? If so, can I get an LGTM or two? If not, what are the deficiencies? |
Sorry, something went wrong.
|
CI is green, LGTM. |
Sorry, something went wrong.
This change fixes a known issue where `maxBuffer` limits by characters rather than bytes. Benchmark added to confirm no performance regression occurs with this change. PR-URL: nodejs#6764 Fixes: nodejs#1901 Reviewed-By: Brian White <mscdex@mscdex.net>
This change fixes a known issue where `maxBuffer` limits by characters rather than bytes. Benchmark added to confirm no performance regression occurs with this change. PR-URL: nodejs#6764 Fixes: nodejs#1901 Reviewed-By: Brian White <mscdex@mscdex.net>
This change fixes a known issue where `maxBuffer` limits by characters rather than bytes. Benchmark added to confirm no performance regression occurs with this change. This necessarily changes default behavior of `stdout` and `stderr` callbacks such that they receive buffers rather than strings. The alternative would be a performance hit on the defaults. Refs: nodejs#6764 Refs: nodejs#1901
|
@thealphanerd If it lands cleanly, yes. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Checklist
child_process test
Affected core subsystem(s)
child_process test
Description of change
This change fixes a known issue where maxBuffer limits by characters
rather than bytes.
Fixes: #1901
Probably need to benchmark it against the current implementation. It's entirely possible no one has done it this way because of performance?