timer: Improve performance of callbacks
authorRuben Verborgh <ruben@verborgh.org>
Sat, 10 Jan 2015 15:49:21 +0000 (16:49 +0100)
committerTrevor Norris <trev.norris@gmail.com>
Wed, 4 Mar 2015 17:08:04 +0000 (10:08 -0700)
setImmediate, setTimeout, and setInterval were called in an inefficient
way, especially in the presence of arguments.  This optimization
improves their performance, with special cases for up to 4 arguments.
Performance of setImmediate increases with 35%, setInterval with 60%,
setTimeout with 70%.

PR-URL: https://github.com/iojs/io.js/pull/406
Reviewed-by: Trevor Norris <trev.norris@gmail.com>
Reviewed-by: Christian Tellnes <christian@tellnes.com>
lib/timers.js

index e3e6428..e5c0c9e 100644 (file)
@@ -173,8 +173,9 @@ exports.active = function(item) {
  */
 
 
-exports.setTimeout = function(callback, after) {
-  var timer;
+exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
+  var timer, i, args;
+  var len = arguments.length;
 
   after *= 1; // coalesce to number or NaN
 
@@ -184,22 +185,38 @@ exports.setTimeout = function(callback, after) {
 
   timer = new Timeout(after);
 
-  if (arguments.length <= 2) {
-    timer._onTimeout = callback;
-  } else {
-    /*
-     * Sometimes setTimeout is called with arguments, EG
-     *
-     *   setTimeout(callback, 2000, "hello", "world")
-     *
-     * If that's the case we need to call the callback with
-     * those args. The overhead of an extra closure is not
-     * desired in the normal case.
-     */
-    var args = Array.prototype.slice.call(arguments, 2);
-    timer._onTimeout = function() {
-      callback.apply(timer, args);
-    };
+  switch (len) {
+    // fast cases
+    case 0:
+    case 1:
+    case 2:
+      timer._onTimeout = callback;
+      break;
+    case 3:
+      timer._onTimeout = function() {
+        callback.call(timer, arg1);
+      };
+      break;
+    case 4:
+      timer._onTimeout = function() {
+        callback.call(timer, arg1, arg2);
+      };
+      break;
+    case 5:
+      timer._onTimeout = function() {
+        callback.call(timer, arg1, arg2, arg3);
+      };
+      break;
+    // slow case
+    default:
+      args = new Array(len - 2);
+      for (i = 2; i < len; i++)
+        args[i - 2] = arguments[i];
+
+      timer._onTimeout = function() {
+        callback.apply(timer, args);
+      };
+      break;
   }
 
   if (process.domain) timer.domain = process.domain;
@@ -222,17 +239,24 @@ exports.clearTimeout = function(timer) {
 };
 
 
-exports.setInterval = function(callback, repeat) {
+exports.setInterval = function(callback, repeat, arg1, arg2, arg3) {
   repeat *= 1; // coalesce to number or NaN
 
   if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
     repeat = 1; // schedule on next tick, follows browser behaviour
   }
 
+  var args, i;
   var timer = new Timeout(repeat);
-  var args = Array.prototype.slice.call(arguments, 2);
+  var len = arguments.length - 2;
   timer._onTimeout = wrapper;
   timer._repeat = true;
+  // Initialize args once for repeated invocation of slow case below
+  if (len > 3) {
+    args = new Array(len);
+    for (i = 0; i < len; i++)
+      args[i] = arguments[i + 2];
+  }
 
   if (process.domain) timer.domain = process.domain;
   exports.active(timer);
@@ -240,7 +264,25 @@ exports.setInterval = function(callback, repeat) {
   return timer;
 
   function wrapper() {
-    callback.apply(this, args);
+    switch (len) {
+      // fast cases
+      case 0:
+        callback.call(this);
+        break;
+      case 1:
+        callback.call(this, arg1);
+        break;
+      case 2:
+        callback.call(this, arg1, arg2);
+        break;
+      case 3:
+        callback.call(this, arg1, arg2, arg3);
+        break;
+      // slow case
+      default:
+        callback.apply(this, args);
+        break;
+    }
     // If callback called clearInterval().
     if (timer._repeat === false) return;
     // If timer is unref'd (or was - it's permanently removed from the list.)
@@ -361,22 +403,44 @@ Immediate.prototype._idleNext = undefined;
 Immediate.prototype._idlePrev = undefined;
 
 
-exports.setImmediate = function(callback) {
+exports.setImmediate = function(callback, arg1, arg2, arg3) {
+  var i, args;
+  var len = arguments.length;
   var immediate = new Immediate();
-  var args, index;
 
   L.init(immediate);
 
-  immediate._onImmediate = callback;
-
-  if (arguments.length > 1) {
-    args = [];
-    for (index = 1; index < arguments.length; index++)
-      args.push(arguments[index]);
-
-    immediate._onImmediate = function() {
-      callback.apply(immediate, args);
-    };
+  switch (len) {
+    // fast cases
+    case 0:
+    case 1:
+      immediate._onImmediate = callback;
+      break;
+    case 2:
+      immediate._onImmediate = function() {
+        callback.call(immediate, arg1);
+      };
+      break;
+    case 3:
+      immediate._onImmediate = function() {
+        callback.call(immediate, arg1, arg2);
+      };
+      break;
+    case 4:
+      immediate._onImmediate = function() {
+        callback.call(immediate, arg1, arg2, arg3);
+      };
+      break;
+    // slow case
+    default:
+      args = new Array(len - 1);
+      for (i = 1; i < len; i++)
+        args[i - 1] = arguments[i];
+
+      immediate._onImmediate = function() {
+        callback.apply(immediate, args);
+      };
+      break;
   }
 
   if (!process._needImmediateCallback) {