Tail call optimization

JavaScript performance comparison

Test case created by John-David Dalton

Info

See https://gist.github.com/1697037.

Preparation code

<script>
function tco1(f) {
  var active = false, value, accumulated, args
  return function accumulator() {
    accumulated = arguments
    if (!active) {
      active = true
      while (accumulated) {
        args = accumulated
        accumulated = null
        value = f.apply(this, args)
      }
      active = false
      return value
    }
  }
}

function tco2(fn) {
  var active, nextArgs;
  return function() {
    var result;
    nextArgs = arguments;
    if (!active) {
      active = true;
      while (nextArgs) {
        result = fn.apply(this, [nextArgs, nextArgs = null][0]);
      }
      active = false;
    }
    return result;
  };
}

function tco3(fn) {
  var active, nextArgs;
  return function() {
    var args, result;
    nextArgs = arguments;
    if (!active) {
      active = true;
      while (nextArgs) {
        args = nextArgs;
        nextArgs = null;
        result = fn.apply(this, args);
      }
      active = false;
    }
    return result;
  };
}

function tco4(fn) {
  var queue;
  return function() {
    var args, result;
    if (queue) {
      queue.push(arguments);
    } else {
      queue = [arguments];
      while ((args = queue.pop())) {
        result = fn.apply(this, args);
      }
      queue = null;
    }
    return result;
  };
}

function tco5(fn) {
  var queue;
  return function() {
    var args, result, index = 0;
    if (queue) {
      queue[queue.length] = arguments;
    } else {
      queue = [arguments];
      while ((args = queue[index++])) {
        result = fn.apply(this, args);
      }
      queue = null;
    }
    return result;
  };
}

function tco6(f) {
  var value, active = false, accumulated = []
  return function accumulator() {
    accumulated.push(arguments)
    if (!active) {
      active = true
      while (accumulated.length) value = f.apply(this, accumulated.shift())
      active = false
      return value
    }
  }
}

function nonRecursiveSum1(x, y) {
  var args, queue = [{ 'x': x, 'y': y }];
  do {
    args = queue.pop();
    x = args.x;
    y = args.y;
    if (y > 0) {
      queue.push({ 'x': x + 1, 'y': y - 1 });
    } else if (y < 0) {
      queue.push({ 'x': x - 1, 'y': y + 1 });
    } else {
      return x;
    }
  } while (true);
}

function nonRecursiveSum2(x, y) {
  var args, index = 0, queue = [{ 'x': x, 'y': y }];
  do {
    args = queue[index++];
    x = args.x;
    y = args.y;
    if (y > 0) {
      queue[queue.length] = { 'x': x + 1, 'y': y - 1 };
    } else if (y < 0) {
      queue[queue.length] = { 'x': x - 1, 'y': y + 1 };
    } else {
      return x;
    }
  } while (true);
}

function nonRecursiveSum3(x, y) {
  var args, index = 0, queue = [[x, y]];
  do {
    args = queue[index++];
    x = args[0];
    y = args[1];
    if (y > 0) {
      queue[queue.length] = [x + 1, y - 1];
    } else if (y < 0) {
      queue[queue.length] = [x - 1, y + 1];
    } else {
      return x;
    }
  } while (true);
}

function nonRecursiveSum4(x, y) {
  var args, queue = [[x, y]];
  do {
    args = queue.pop();
    x = args[0];
    y = args[1];
    if (y > 0) {
      queue.push([x + 1, y - 1]);
    } else if (y < 0) {
      queue.push([x - 1, y + 1]);
    } else {
      return x;
    }
  } while (true);
}
</script>
<script>
Benchmark.prototype.setup = function() {
    var sum1 = tco1(function(x, y) {
      return y > 0 ? sum1(x + 1, y - 1) :
             y < 0 ? sum1(x - 1, y + 1) :
             x
    });
   
    var sum2 = tco2(function(x, y) {
      return y > 0 ? sum2(x + 1, y - 1) :
             y < 0 ? sum2(x - 1, y + 1) :
             x
    });
   
    var sum3 = tco3(function(x, y) {
      return y > 0 ? sum3(x + 1, y - 1) :
             y < 0 ? sum3(x - 1, y + 1) :
             x
    });
   
    var sum4 = tco4(function(x, y) {
      return y > 0 ? sum4(x + 1, y - 1) :
             y < 0 ? sum4(x - 1, y + 1) :
             x
    });
   
    var sum5 = tco5(function(x, y) {
      return y > 0 ? sum5(x + 1, y - 1) :
             y < 0 ? sum5(x - 1, y + 1) :
             x
    });
   
    var sum6 = tco6(function(x, y) {
      return y > 0 ? sum6(x + 1, y - 1) :
             y < 0 ? sum6(x - 1, y + 1) :
             x
    });
   
    var nonRecursiveSum1 = window.nonRecursiveSum1;
    var nonRecursiveSum2 = window.nonRecursiveSum2;
    var nonRecursiveSum3 = window.nonRecursiveSum3;
    var nonRecursiveSum4 = window.nonRecursiveSum4;
};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
tco1
sum1(20, 1000);
pending…
tco2
sum2(20, 1000);
pending…
tco3
sum3(20, 1000);
pending…
tco4
sum4(20, 1000);
pending…
tco5
sum5(20, 1000);
pending…
tco6
sum6(20, 1000);
pending…
nrs1
nonRecursiveSum1(20, 1000);
pending…
nrs2
nonRecursiveSum2(20, 1000);
pending…
nrs3
nonRecursiveSum3(20, 1000);
pending…
nrs4
nonRecursiveSum4(20, 1000);
pending…

You can edit these tests or add even more tests to this page by appending /edit to the URL.

Compare results of other browsers

0 comments

Add a comment