-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
http: optimize outgoing requests #605
http: optimize outgoing requests #605
Conversation
var e = this.outputEncodings.shift(); | ||
var cb = this.outputCallbacks.shift(); | ||
this.connection.write(c, e, cb); | ||
this.output = []; |
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 previous version didn't create new arrays. you can just do this.output.length = 0;
. the same for the rest of these.
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 can see it going either way. .length
is a (very magic!) setter in V8; the array.length = 0
trick might well be slower than creating a new empty array, even if the latter produces a little more garbage.
Using array.pop()
here could help as well, it's normally faster than array.shift()
because it needs to do less work.
EDIT: To be clear, switching to array.pop()
requires that the arrays are built up in reverse order as well, of course.
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 previous version didn't create new arrays, instead of shift()
ed off all the values - basically emptying the array, which for the new version, I simply emptied the array by making a new one.
JSPerf says that using = [];
instead of .length = 0;
is faster, but if you know differently I'd be open to hearing about it.
Building the arrays in reverse order by using array.unshift()
would, I think, impact other benchmarks in a bad way to the point of making that optimization a de-optimization though.
FWIW some further optimizations could be done, such as caching |
var outputLength = this.output.length; | ||
if (outputLength) { | ||
for (var i = 0; i < outputLength; i++) { | ||
var cb = this.outputCallbacks[i]; |
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.
initialize this variable outside the loop so it's only setting the value each time and not creating garbage, and does this cb
variable need to exist at all?
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.
You mean hoist the var cb
outside the loop? It shouldn't matter, V8's flow analysis doesn't care. The variable as such probably doesn't even exist in the generated machine code.
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 initially just inlined cb
in the function call to deal with the 80 line limit, but I just realized I could just wrap the function.
Whoops, I'll fix that.
@@ -408,7 +407,7 @@ OutgoingMessage.prototype.write = function(chunk, encoding, callback) { | |||
return true; | |||
} | |||
|
|||
if (!util.isString(chunk) && !util.isBuffer(chunk)) { | |||
if (!(typeof chunk === 'string' || util.isBuffer(chunk))) { |
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.
You might as well change util.isBuffer(chunk)
to chunk instanceof Buffer
while you're here.
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.
Ah, didn't realize that. I think there's a few other places I could pop that in, thanks.
d0b9600
to
b3a7a2a
Compare
Pushed up an MAJOR improvement! Now I'm getting into the ~5800 req/sec range for some, which dips down to only ~4400 req/sec on the worst ones - a major improvement on before. Graph. |
👍 |
@@ -78,11 +78,12 @@ exports.OutgoingMessage = OutgoingMessage; | |||
OutgoingMessage.prototype.setTimeout = function(msecs, callback) { | |||
if (callback) | |||
this.on('timeout', callback); | |||
if (!this.socket) { | |||
|
|||
if (!this.socket) |
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.
this is hazardous styling IMO, personally I'd prefer braces where the body of a conditional spans more than one line, even if it's effectively a one-liner, simply for clarity
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.
Agreed. Most major style guides advocate for always having curlies to accompany conditional logic, length irrelevant.
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 actually do agree with you but I changed it to match the function declaration just a few lines below. I'll update with a commit that fixes this one and the one below, too.
My only other general comment is there are a lot of variables being initialized inside block scopes. I've found that moving these declarations to the topmost level (as they would be hoisted by the interpreter) makes code much more readable and you don't run into issues like accidentally re-declaring variables. |
@yaycmyk True, but a linter like jshint can catch things like that no? |
@mscdex Sure, but it's more being courteous to the next developer than a general coding problem. Legibility > brevity IMO -- declaring everything at the onset gives you a quick birds' eye view of your function and makes it easier to grok what the important bits of data are. |
var outputEncodings = this.outputEncodings; | ||
var outputCallbacks = this.outputCallbacks; | ||
for (var i = 0; i < outputLength; i++) { | ||
ret = this.socket.write(output[i], outputEncodings[i], |
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.
this.socket
should be able to be cached too.
b3a7a2a
to
ff8563a
Compare
Pushed up another update with some hoisted variables per @yaycmyk and @mscdex, plus a few other extraneous changes. |
this.socket.setTimeout(msecs); | ||
} |
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.
No unrelated style changes, please. It makes the diff noisier than it should be.
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.
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.
Please undo them, it complicates code archeology. The guideline is to refrain from stylistic changes unless you're updating the code that you're touching anyway.
Left some style-related comments but the general thrust seems alright to me. Can you post benchmark numbers? The http tests are I/O-sensitive, you may have to run them a few times to check if the numbers are stable. |
ff8563a
to
91a6e60
Compare
Pushed up I actually upped the duration to twenty seconds for each test, that way I shouldn't have to redo the tests over and over again; here are the results: HEAD (
PR (
|
@brendanashworth Nice! Across the board improvements 👏 |
if (headers) { | ||
var keys = Object.keys(headers); | ||
var isArray = util.isArray(headers); | ||
var isArray = Array.isArray(headers); |
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.
We could probably get a further speedup from switching these to {var} instanceof Array
- http://jsperf.com/array-isarray-vs-instanceof-array/5
benchmark puts it at 2x faster, since we're making micro-optimizations :)
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.
Changed for all three uses!
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.
instanceof
doesn't work well:
var obj = { __proto__ : [] };
console.log(obj instanceof Array); // true
console.log(Array.isArray(obj)); // false
var arr = require('vm').runInNewContext('[]');
console.log(arr instanceof Array); // false
console.log(Array.isArray(arr)); // true
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.
Reverted back to Array.isArray
.
e571203
to
af5e82a
Compare
Hows it looking now? |
Looks like it needs a rebase, code's good though! |
af5e82a
to
f5f6550
Compare
Rebased. |
LGTM. Love the perf direction here 👍 |
this._buffer(data, encoding, callback); | ||
return false; | ||
var outputLength = this.output.length; | ||
if (outputLength) { |
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 speculate that using outputLength > 0
here will be infinitesimally faster.
f5f6550
to
f763504
Compare
Nit picks should be fixed, @bnoordhuis . |
@@ -487,7 +492,7 @@ OutgoingMessage.prototype.end = function(data, encoding, callback) { | |||
encoding = null; | |||
} | |||
|
|||
if (data && typeof data !== 'string' && !(data instanceof Buffer)) { | |||
if (data && !(typeof data === 'string' || data instanceof Buffer)) { |
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.
Can you undo these changes as well, please?
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.
This was less of a style change and more of an optimization, leaving the data instanceof Buffer
to not be called if it was a string. Would you still like them removed?
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'm not sure if it's worth optimizing for the uncommon case ('bug case' really) but I'll leave that at your discretion. I do think it's a little harder to read now, though.
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.
Definitely harder to read.
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.
Okay, I'll revert this and the other like line.
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.
Would you like me to also change this line?
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.
Yes, please.
f763504
to
5ff7d4f
Compare
This commit does some small optimization changes on `lib/_http_outgoing.js`. These include switching from `while` loops to `for` loops, moving away from `util` to `typeof` checks, and removing dead code. It also includes variable caches to avoid lookups and generic style changes. All in all, much faster execution. It gets an across the board increase in req/sec on the benchmarks, from my experience about a 10% increase.
5ff7d4f
to
8742b8a
Compare
|
Bumpity bump. Hows it looking? |
LGTM |
3 similar comments
LGTM |
LGTM |
LGTM |
This commit does some small optimization changes on `lib/_http_outgoing.js`. These include switching from `while` loops to `for` loops, moving away from `util` to `typeof` checks, and removing dead code. It also includes variable caches to avoid lookups and generic style changes. All in all, much faster execution. It gets an across the board increase in req/sec on the benchmarks, from my experience about a 10% increase. PR-URL: #605 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Nicu Micleușanu <[email protected]> Reviewed-By: Christian Vaagland Tellnes <[email protected]> Reviewed-By: Brian White <[email protected]>
Closed in |
This commit does some small optimization changes on
lib/_http_outgoing.js
. These include switching fromwhile
loops tofor
loops, moving away fromutil
totypeof
checks, and removingdead code.
Here is a graph of the benchmark
change (duration = 20, ran twice each). Changes aren't very visible in
smaller packets - at 32 bytes, only about 1% more requests can be sent,
but it increases as the packets increase in size; 1024 has 4.7% more
throughput than before.
Here is a graph of the same
benchmark but sorted by encoding type. As it is shown, buffers get the
biggest speed increase, then utf-8, then ASCII.