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

streams: isNaN() in howMuchToRead() is slow #936

Closed
bnoordhuis opened this issue Feb 24, 2015 · 12 comments
Closed

streams: isNaN() in howMuchToRead() is slow #936

bnoordhuis opened this issue Feb 24, 2015 · 12 comments
Labels
stream Issues and PRs related to the stream subsystem.

Comments

@bnoordhuis
Copy link
Member

             6.93%
                LazyCompile:*isNaN native v8natives.js:67
                LazyCompile:*howMuchToRead _stream_readable.js:208
                LazyCompile:*Readable.read _stream_readable.js:246
                LazyCompile:*readableAddChunk _stream_readable.js:122
                LazyCompile:*onread net.js:495
                Builtin:ArgumentsAdaptorTrampoline
                Builtin:JSEntryTrampoline
                Stub:JSEntryStub
                v8::internal::Execution::Call
                v8::Function::Call
                node::AsyncWrap::MakeCallback
                node::StreamWrap::OnRead
                uv__read
                uv__stream_io
                uv__io_poll
                uv_run
                node::Start
                __libc_start_main
                0xbf4e258d4c544155

This isNaN() call frequently pops up in I/O-heavy benchmarks as the top cost center.

/cc @chrisdickinson

@bnoordhuis bnoordhuis added the stream Issues and PRs related to the stream subsystem. label Feb 24, 2015
@chrisdickinson
Copy link
Contributor

Guessing this is due to polymorphic n in read(n) calls, which may be a number or undefined. We might see if it's possible to coerce n === undefined to n = -1 and check for that value in howMuchToRead. In fact, I may just go do that now ...

@chrisdickinson
Copy link
Contributor

What benchmark are you using, out of curiosity?

@mscdex
Copy link
Contributor

mscdex commented Feb 25, 2015

What if you replace n === null || isNaN(n) with n == null || n !== n. That should significantly improve speed while maintaining compatibility with isNaN().

@chrisdickinson
Copy link
Contributor

Hm, I tried replacing the undefined value with -1, which managed to knock howMuchToRead out of the running for net-pipe, but I wasn't able to see much of a difference perf-wise. (The reasoning behind using -1 was to see if I could make the read / howMuchToRead implementation monomorphic.)

@bnoordhuis
Copy link
Member Author

What benchmark are you using, out of curiosity?

@chrisdickinson That's from perf record -c 50000 -e cpu-clock -g -i out/Release/iojs --perf_basic_prof benchmark/http_simple_auto.js -k -t 10 -c 100 -n 1000000

@chrisdickinson
Copy link
Contributor

If you're interested, here's the patch I was testing:

diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index bf73647..2a27faf 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -212,7 +212,7 @@ function howMuchToRead(n, state) {
   if (state.objectMode)
     return n === 0 ? 0 : 1;

-  if (n === null || isNaN(n)) {
+  if (n === -1 || n === null || isNaN(n)) {
     // only flow one buffer at a time
     if (state.flowing && state.buffer.length)
       return state.buffer[0].length;
@@ -244,11 +244,16 @@ function howMuchToRead(n, state) {

 // you can override either this method, or the async _read(n) below.
 Readable.prototype.read = function(n) {
+  if (n === undefined) n = -1;
+  return Readable_read(this, n);
+}
+
+function Readable_read(stream, n) {
   debug('read', n);
-  var state = this._readableState;
+  var state = stream._readableState;
   var nOrig = n;

-  if (typeof n !== 'number' || n > 0)
+  if (n !== 'number' || n === -1 || n > 0)
     state.emittedReadable = false;

   // if we're doing read(0) to trigger a readable event, but we
@@ -259,9 +264,9 @@ Readable.prototype.read = function(n) {
       (state.length >= state.highWaterMark || state.ended)) {
     debug('read: emitReadable', state.length, state.ended);
     if (state.length === 0 && state.ended)
-      endReadable(this);
+      endReadable(stream);
     else
-      emitReadable(this);
+      emitReadable(stream);
     return null;
   }

@@ -270,7 +275,7 @@ Readable.prototype.read = function(n) {
   // if we've ended, and we're now clear, then finish it up.
   if (n === 0 && state.ended) {
     if (state.length === 0)
-      endReadable(this);
+      endReadable(stream);
     return null;
   }

@@ -321,7 +326,7 @@ Readable.prototype.read = function(n) {
     if (state.length === 0)
       state.needReadable = true;
     // call internal read method
-    this._read(state.highWaterMark);
+    stream._read(state.highWaterMark);
     state.sync = false;
   }

@@ -350,10 +355,10 @@ Readable.prototype.read = function(n) {

   // If we tried to read() past the EOF, then emit end on the next tick.
   if (nOrig !== n && state.ended && state.length === 0)
-    endReadable(this);
+    endReadable(stream);

   if (ret !== null)
-    this.emit('data', ret);
+    stream.emit('data', ret);

   return ret;
 };
@@ -731,7 +736,7 @@ function flow(stream) {
   debug('flow', state.flowing);
   if (state.flowing) {
     do {
-      var chunk = stream.read();
+      var chunk = stream.read(-1);
     } while (null !== chunk && state.flowing);
   }
 }

@Fishrock123
Copy link
Contributor

As @mscdex suggested, this patch should do. I don't have access to linux perf tools, can someone check it?

diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 1de5559..731973f 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -211,7 +211,8 @@ function howMuchToRead(n, state) {
   if (state.objectMode)
     return n === 0 ? 0 : 1;

-  if (n === null || isNaN(n)) {
+  // fast check for NaN with val !== val
+  if (n === null || n !== n) {
     // only flow one buffer at a time
     if (state.flowing && state.buffer.length)
       return state.buffer[0].length;

@chrisdickinson the typeof should inline quite well, I doubt there'd be any valuable perf gain from that patch.

@chrisdickinson
Copy link
Contributor

@Fishrock123 The typeof should inline, but by calling the howMuchToRead function with both undefined and number types it (needlessly) becomes polymorphic, the elimination of which was the goal behind coercing n === undefined to n = -1 in Readable.prototype.read.

@Fishrock123
Copy link
Contributor

but by calling the howMuchToRead function with both undefined and number types it (needlessly) becomes polymorphic

Ahh. I guess that'd be a separate thing to pref check then though.

@Trott
Copy link
Member

Trott commented Feb 4, 2016

Is this fixed/closed by #1925? Or is this still an issue?

@bnoordhuis
Copy link
Member Author

It still comes up in profiles from time to time but it hasn't bothered me enough yet to revisit #1925. If you want to close it, you have my blessing.

@Trott
Copy link
Member

Trott commented Feb 4, 2016

OK, I'll close it then. It can always be re-opened. Thanks.

@Trott Trott closed this as completed Feb 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

No branches or pull requests

5 participants