node: improve nextTick performance
authorBrian White <mscdex@mscdex.net>
Fri, 1 May 2015 18:08:02 +0000 (14:08 -0400)
committerBrian White <mscdex@mscdex.net>
Sat, 2 May 2015 01:27:34 +0000 (21:27 -0400)
This commit uses separate functions to isolate deopts caused by
try-catches and avoids fn.apply() for callbacks with small numbers
of arguments.

These changes improve performance by ~1-40% in the various
nextTick benchmarks.

PR-URL: https://github.com/iojs/io.js/pull/1571
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
benchmark/misc/next-tick-breadth-args.js [new file with mode: 0644]
benchmark/misc/next-tick-depth-args.js [new file with mode: 0644]
src/node.js
test/message/nexttick_throw.out
test/message/stdin_messages.out

diff --git a/benchmark/misc/next-tick-breadth-args.js b/benchmark/misc/next-tick-breadth-args.js
new file mode 100644 (file)
index 0000000..e617fcf
--- /dev/null
@@ -0,0 +1,37 @@
+'use strict';
+
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+  millions: [2]
+});
+
+function main(conf) {
+  var N = +conf.millions * 1e6;
+  var n = 0;
+
+  function cb1(arg1) {
+    n++;
+    if (n === N)
+      bench.end(n / 1e6);
+  }
+  function cb2(arg1, arg2) {
+    n++;
+    if (n === N)
+      bench.end(n / 1e6);
+  }
+  function cb3(arg1, arg2, arg3) {
+    n++;
+    if (n === N)
+      bench.end(n / 1e6);
+  }
+
+  bench.start();
+  for (var i = 0; i < N; i++) {
+    if (i % 3 === 0)
+      process.nextTick(cb3, 512, true, null);
+    else if (i % 2 === 0)
+      process.nextTick(cb2, false, 5.1);
+    else
+      process.nextTick(cb1, 0);
+  }
+}
diff --git a/benchmark/misc/next-tick-depth-args.js b/benchmark/misc/next-tick-depth-args.js
new file mode 100644 (file)
index 0000000..066b483
--- /dev/null
@@ -0,0 +1,48 @@
+'use strict';
+
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+  millions: [2]
+});
+
+process.maxTickDepth = Infinity;
+
+function main(conf) {
+  var n = +conf.millions * 1e6;
+
+  function cb3(arg1, arg2, arg3) {
+    if (--n) {
+      if (n % 3 === 0)
+        process.nextTick(cb3, 512, true, null);
+      else if (n % 2 === 0)
+        process.nextTick(cb2, false, 5.1);
+      else
+        process.nextTick(cb1, 0);
+    } else
+      bench.end(+conf.millions);
+  }
+  function cb2(arg1, arg2) {
+    if (--n) {
+      if (n % 3 === 0)
+        process.nextTick(cb3, 512, true, null);
+      else if (n % 2 === 0)
+        process.nextTick(cb2, false, 5.1);
+      else
+        process.nextTick(cb1, 0);
+    } else
+      bench.end(+conf.millions);
+  }
+  function cb1(arg1) {
+    if (--n) {
+      if (n % 3 === 0)
+        process.nextTick(cb3, 512, true, null);
+      else if (n % 2 === 0)
+        process.nextTick(cb2, false, 5.1);
+      else
+        process.nextTick(cb1, 0);
+    } else
+      bench.end(+conf.millions);
+  }
+  bench.start();
+  process.nextTick(cb1, true);
+}
index 1cb71c4..5cf56f6 100644 (file)
     // Run callbacks that have no domain.
     // Using domains will cause this to be overridden.
     function _tickCallback() {
-      var callback, threw, tock;
+      var callback, args, tock;
 
       do {
         while (tickInfo[kIndex] < tickInfo[kLength]) {
           tock = nextTickQueue[tickInfo[kIndex]++];
           callback = tock.callback;
-          threw = true;
-          try {
-            if (tock.args === undefined)
-              callback();
-            else
-              callback.apply(null, tock.args);
-            threw = false;
-          } finally {
-            if (threw)
-              tickDone();
+          args = tock.args;
+          // Using separate callback execution functions helps to limit the
+          // scope of DEOPTs caused by using try blocks and allows direct
+          // callback invocation with small numbers of arguments to avoid the
+          // performance hit associated with using `fn.apply()`
+          if (args === undefined) {
+            doNTCallback0(callback);
+          } else {
+            switch (args.length) {
+              case 1:
+                doNTCallback1(callback, args[0]);
+                break;
+              case 2:
+                doNTCallback2(callback, args[0], args[1]);
+                break;
+              case 3:
+                doNTCallback3(callback, args[0], args[1], args[2]);
+                break;
+              default:
+                doNTCallbackMany(callback, args);
+            }
           }
           if (1e4 < tickInfo[kIndex])
             tickDone();
     }
 
     function _tickDomainCallback() {
-      var callback, domain, threw, tock;
+      var callback, domain, args, tock;
 
       do {
         while (tickInfo[kIndex] < tickInfo[kLength]) {
           tock = nextTickQueue[tickInfo[kIndex]++];
           callback = tock.callback;
           domain = tock.domain;
+          args = tock.args;
           if (domain)
             domain.enter();
-          threw = true;
-          try {
-            if (tock.args === undefined)
-              callback();
-            else
-              callback.apply(null, tock.args);
-            threw = false;
-          } finally {
-            if (threw)
-              tickDone();
+          // Using separate callback execution functions helps to limit the
+          // scope of DEOPTs caused by using try blocks and allows direct
+          // callback invocation with small numbers of arguments to avoid the
+          // performance hit associated with using `fn.apply()`
+          if (args === undefined) {
+            doNTCallback0(callback);
+          } else {
+            switch (args.length) {
+              case 1:
+                doNTCallback1(callback, args[0]);
+                break;
+              case 2:
+                doNTCallback2(callback, args[0], args[1]);
+                break;
+              case 3:
+                doNTCallback3(callback, args[0], args[1], args[2]);
+                break;
+              default:
+                doNTCallbackMany(callback, args);
+            }
           }
           if (1e4 < tickInfo[kIndex])
             tickDone();
       } while (tickInfo[kLength] !== 0);
     }
 
+    function doNTCallback0(callback) {
+      var threw = true;
+      try {
+        callback();
+        threw = false;
+      } finally {
+        if (threw)
+          tickDone();
+      }
+    }
+
+    function doNTCallback1(callback, arg1) {
+      var threw = true;
+      try {
+        callback(arg1);
+        threw = false;
+      } finally {
+        if (threw)
+          tickDone();
+      }
+    }
+
+    function doNTCallback2(callback, arg1, arg2) {
+      var threw = true;
+      try {
+        callback(arg1, arg2);
+        threw = false;
+      } finally {
+        if (threw)
+          tickDone();
+      }
+    }
+
+    function doNTCallback3(callback, arg1, arg2, arg3) {
+      var threw = true;
+      try {
+        callback(arg1, arg2, arg3);
+        threw = false;
+      } finally {
+        if (threw)
+          tickDone();
+      }
+    }
+
+    function doNTCallbackMany(callback, args) {
+      var threw = true;
+      try {
+        callback.apply(null, args);
+        threw = false;
+      } finally {
+        if (threw)
+          tickDone();
+      }
+    }
+
     function TickObject(c, args) {
       this.callback = c;
       this.domain = process.domain || null;
index baced2c..1dd6069 100644 (file)
@@ -4,6 +4,7 @@
         ^
 ReferenceError: undefined_reference_error_maker is not defined
     at *test*message*nexttick_throw.js:*:*
+    at doNTCallback0 (node.js:*:*)
     at process._tickCallback (node.js:*:*)
     at Function.Module.runMain (module.js:*:*)
     at startup (node.js:*:*)
index cbdc1fd..92d09a0 100644 (file)
@@ -12,6 +12,7 @@ SyntaxError: Strict mode code may not include a with statement
     at emitNone (events.js:*:*)
     at Socket.emit (events.js:*:*)
     at endReadableNT (_stream_readable.js:*:*)
+    at doNTCallback2 (node.js:*:*)
     at process._tickCallback (node.js:*:*)
 42
 42
@@ -29,7 +30,7 @@ Error: hello
     at emitNone (events.js:*:*)
     at Socket.emit (events.js:*:*)
     at endReadableNT (_stream_readable.js:*:*)
-    at process._tickCallback (node.js:*:*)
+    at doNTCallback2 (node.js:*:*)
 
 [stdin]:1
 throw new Error("hello")
@@ -44,7 +45,7 @@ Error: hello
     at emitNone (events.js:*:*)
     at Socket.emit (events.js:*:*)
     at endReadableNT (_stream_readable.js:*:*)
-    at process._tickCallback (node.js:*:*)
+    at doNTCallback2 (node.js:*:*)
 100
 
 [stdin]:1
@@ -60,7 +61,7 @@ ReferenceError: y is not defined
     at emitNone (events.js:*:*)
     at Socket.emit (events.js:*:*)
     at endReadableNT (_stream_readable.js:*:*)
-    at process._tickCallback (node.js:*:*)
+    at doNTCallback2 (node.js:*:*)
 
 [stdin]:1
 var ______________________________________________; throw 10