Function Overloading

JavaScript performance comparison

Test case created by Unkn0wn

Info

Tests the speed of function overloading (when testing types) against calling methods directly. Results show a massive speed penalty for function overloading in JavaScript.

I've also taken the liberty of benchmarking the approach of passing an object and testing it's properties, i.e. Func({point: point, dimension: dimension}), but that approach is a great deal slower than function overloading.

You can also see the penalty of calling Function.apply(this, arguments) compared to calling the method directly. This should inform your decision process greatly when deciding what approach to take with 'function overloading'.

Preparation code

<script>
  function Dimension() {}

  function Point() {}

  var Util = {};
  // \fn Redirect(args, func, ...)
  // Will call the given function if the args (arguments) match the
  // function signature determined by the variable arguments after the func
  // parameter. Example:
  //  Util.Redirect.apply(this, [arguments, DoFunction, 'number', Point, 'number']).
  // or:
  //  Util.Redirect(arguments, func, typeof(0), Point, typeof(0));
  //
  // The example above will call DoFunction(arguments), passing the this pointer
  // if args matches the ('number', Point, 'number') signature.
  //  \returns null on failure, otherwise the it returns the result inside of an
  //    array (this allows redirected functions returning null to not be confused
  //    with a failed redirect).
  Util.Redirect = function(args, func) {
    'use strict';
    // Count of arguments for this function before varargs.
    var REDIRECT_ARGUMENT_COUNT = 2;

    if (arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
      return null;
    }

    for (var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
      var argsIndex = i - REDIRECT_ARGUMENT_COUNT;

      var currentArgument = args[argsIndex];
      var currentType = arguments[i];

      // Adjust the type to compare with
      if (typeof(currentType) === 'object') {
        // Turn currentType from object to function.
        currentType = currentType.constructor;
      }

      if (typeof(currentType) === 'number') {
        currentType = 'number';
      }

      if (typeof(currentType) === 'string' && currentType === '') {
        currentType = 'string';
      }

      // Test current argument against type.
      if (typeof(currentType) === 'function') {
        if (!(currentArgument instanceof currentType)) {
          // Fail !!
          return null;
        }
      } else {
        // Argument is a primitive (and represented as a string) ...
        if (typeof(currentArgument) !== currentType) {
          // Fail !!
          return null;
        }
      }

    }

    // All tests have passed.
    return [func.apply(this, args)];

  }


  function FuncPoint(point) {}

  function FuncPointDispatch(point) {
    FuncPoint.apply(this, arguments);
  }

  function FuncDimension(dimension) {}

  function FuncDimensionDispatch(dimension) {
    FuncDimension.apply(this, arguments);
  }

  function FuncDimensionPoint(dimension, point) {}

  function FuncDimensionPointDispatch(dimension, point) {
    FuncDimensionPoint.apply(this, arguments);
  }

  function FuncXYWidthHeight(x, y, width, height) {}

  function FuncXYWidthHeightDispatch(x, y, width, height) {
    FuncXYWidthHeight.apply(this, arguments);
  }

  function Func() {
    'use strict';
    Util.Redirect(arguments, FuncPoint, Point);
    Util.Redirect(arguments, FuncDimension, Dimension);
    Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
    Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
  }

  function FuncArgTestProperty(arg) {
    'use strict';
    var hasPoint = arg.hasOwnProperty('point'),
        hasDimension = arg.hasOwnProperty('dimension'),
        hasX = arg.hasOwnProperty('x'),
        hasY = arg.hasOwnProperty('y'),
        hasWidth = arg.hasOwnProperty('width'),
        hasHeight = arg.hasOwnProperty('height'),
        hasXYWidthHeight = hasX & hasY & hasWidth & hasHeight;

    if (hasPoint & !hasDimension) {
      FuncPoint.apply(this, [arg.point]);
    } else if (!hasPoint & hasDimension) {
      FuncDimension.apply(this, [arg.dimension]);
    } else if (hasPoint & hasDimension) {
      FuncDimensionPoint.apply(this, [arg.dimension, arg.point]);
    } else if (hasXYWidthHeight) {
      FuncXYWidthHeight.apply(this, [arg.x, arg.y, arg.width, arg.height]);
    }
  }

  function FuncInTest(arg) {
    'use strict';
    var hasPoint = 'point' in arg,
        hasDimension = 'dimension' in arg,
        hasX = 'x' in arg,
        hasY = 'y' in arg,
        hasWidth = 'width' in arg,
        hasHeight = 'height' in arg,
        hasXYWidthHeight = hasX & hasY & hasWidth & hasHeight;

    if (hasPoint & !hasDimension) {
      FuncPoint.apply(this, [arg.point]);
    } else if (!hasPoint & hasDimension) {
      FuncDimension.apply(this, [arg.dimension]);
    } else if (hasPoint & hasDimension) {
      FuncDimensionPoint.apply(this, [arg.dimension, arg.point]);
    } else if (hasXYWidthHeight) {
      FuncXYWidthHeight.apply(this, [arg.x, arg.y, arg.width, arg.height]);
    }
  }
</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
Function overloading
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point);
Func(0, 0, 0, 0);
pending…
Direct method calling
FuncPoint(new Point());
FuncDimension(new Dimension());
FuncDimensionPoint(new Dimension(), new Point());
FuncXYWidthHeight(0, 0, 0, 0);
pending…
Passing object, test hasOwnProperty
FuncArgTestProperty(new Point());
FuncArgTestProperty(new Dimension());
FuncArgTestProperty(new Dimension(), new Point());
FuncArgTestProperty(0, 0, 0, 0);
pending…
Passing object, test 'in' object
FuncInTest({
  point: new Point()
});
FuncInTest({
  dimension: new Dimension()
});
FuncInTest({
  dimension: new Dimension(),
  point: new Point()
});
FuncInTest({
  x: 0,
  y: 0,
  width: 0,
  height: 0
});
pending…
Direct method calling +apply
// Func*Dispatch just calls Func*.apply(this, arguments).
FuncPointDispatch(new Point());
FuncDimensionDispatch(new Dimension());
FuncDimensionPointDispatch(new Dimension(), new Point());
FuncXYWidthHeightDispatch(0, 0, 0, 0);
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment