Performance of different Eventemitters (v6)

Revision 6 of this benchmark created on


Description

performance of different EventEmitters

Preparation HTML

<script src="http://rawgithub.com/Wolfy87/EventEmitter/master/EventEmitter.js"></script>
<script>
module = exports = undefined;
</script>
<script src="http://rawgithub.com/hij1nx/EventEmitter2/master/lib/eventemitter2.js"></script>
<script>
  // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  var isArray = Array.isArray;

  function EventEmitter1() {}

  // By default EventEmitters will print a warning if more than
  // 10 listeners are added to it. This is a useful default which
  // helps finding memory leaks.
  //
  // Obviously not all Emitters should be limited to 10. This function allows
  // that to be increased. Set to zero for unlimited.
  var defaultMaxListeners = 10;
  EventEmitter1.prototype.setMaxListeners = function(n) {
    if (!this._events) this._events = {};
    this._events.maxListeners = n;
  };


  EventEmitter1.prototype.emit = function() {
    var type = arguments[0];
    // If there is no 'error' event listener then throw.
    if (type === 'error') {
      if (!this._events || !this._events.error || (isArray(this._events.error) && !this._events.error.length)) {
        if (arguments[1] instanceof Error) {
          throw arguments[1]; // Unhandled 'error' event
        } else {
          throw new Error("Uncaught, unspecified 'error' event.");
        }
        return false;
      }
    }

    if (!this._events) return false;
    var handler = this._events[type];
    if (!handler) return false;

    if (typeof handler == 'function') {
      switch (arguments.length) {
        // fast cases
      case 1:
        handler.call(this);
        break;
      case 2:
        handler.call(this, arguments[1]);
        break;
      case 3:
        handler.call(this, arguments[1], arguments[2]);
        break;
        // slower
      default:
        var l = arguments.length;
        var args = new Array(l - 1);
        for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
        handler.apply(this, args);
      }
      return true;

    } else if (isArray(handler)) {
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

      var listeners = handler.slice();
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].apply(this, args);
      }
      return true;

    } else {
      return false;
    }
  };

  // EventEmitter1 is defined in src/node_events.cc
  // EventEmitter1.prototype.emit() is also defined there.
  EventEmitter1.prototype.addListener = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('addListener only takes instances of Function');
    }

    if (!this._events) this._events = {};

    // To avoid recursion in the case that type == "newListeners"! Before
    // adding it to the listeners, first emit "newListeners".
    this.emit('newListener', type, listener);

    if (!this._events[type]) {
      // Optimize the case of one listener. Don't need the extra array object.
      this._events[type] = listener;
    } else if (isArray(this._events[type])) {

      // If we've already got an array, just append.
      this._events[type].push(listener);

      // Check for listener leak
      if (!this._events[type].warned) {
        var m;
        if (this._events.maxListeners !== undefined) {
          m = this._events.maxListeners;
        } else {
          m = defaultMaxListeners;
        }

        if (m && m > 0 && this._events[type].length > m) {
          this._events[type].warned = true;
          console.error('(node) warning: possible EventEmitter1 memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length);
          console.trace();
        }
      }
    } else {
      // Adding the second element, need to change to array.
      this._events[type] = [this._events[type], listener];
    }

    return this;
  };

  EventEmitter1.prototype.on = EventEmitter1.prototype.addListener;

  EventEmitter1.prototype.once = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('.once only takes instances of Function');
    }

    var self = this;

    function g() {
      self.removeListener(type, g);
      listener.apply(this, arguments);
    };

    g.listener = listener;
    self.on(type, g);

    return this;
  };

  EventEmitter1.prototype.removeListener = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('removeListener only takes instances of Function');
    }

    // does not use listeners(), so no side effect of creating _events[type]
    if (!this._events || !this._events[type]) return this;

    var list = this._events[type];

    if (isArray(list)) {
      var position = -1;
      for (var i = 0, length = list.length; i < length; i++) {
        if (list[i] === listener || (list[i].listener && list[i].listener === listener)) {
          position = i;
          break;
        }
      }

      if (position < 0) return this;
      list.splice(position, 1);
      if (list.length == 0) delete this._events[type];
    } else if (list === listener || (list.listener && list.listener === listener)) {
      delete this._events[type];
    }

    return this;
  };

  EventEmitter1.prototype.removeAllListeners = function(type) {
    if (arguments.length === 0) {
      this._events = {};
      return this;
    }

    // does not use listeners(), so no side effect of creating _events[type]
    if (type && this._events && this._events[type]) this._events[type] = null;
    return this;
  };

  EventEmitter1.prototype.listeners = function(type) {
    if (!this._events) this._events = {};
    if (!this._events[type]) this._events[type] = [];
    if (!isArray(this._events[type])) {
      this._events[type] = [this._events[type]];
    }
    return this._events[type];
  };
</script>
<script>
ShokaiEventEmitter = function(){
  var self = this;
  this.apply = function(target, prefix){
    if(!prefix) prefix = "";
    for(var func in self){
      if(self.hasOwnProperty(func) && func !== "apply"){
        target[prefix+func] = this[func];
      }
    }
  };
  this.__events = new Array();
  this.on = function(type, listener, opts){
    if(typeof listener !== "function") return;
    var event_id = self.__events.length > 0 ? 1 + self.__events[self.__events.length-1].id : 0
    var params = {
      id: event_id,
      type: type,
      listener: listener
    };
    for(i in opts){
      if(!params[i]) params[i] = opts[i];
    };
    self.__events.push(params);
    return event_id;
  };

  this.once = function(type, listener){
    self.on(type, listener, {once: true});
  };

  this.emit = function(type, data){
    for(var i = 0; i < self.__events.length; i++){
      var e = self.__events[i];
      switch(e.type){
      case type:
        e.listener(data);
        if(e.once) e.type = null;
        break
      case "*":
        e.listener(type, data);
        if(e.once) e.type = null;
        break
      }
    }
    self.removeListener();
  };

  this.removeListener = function(id_or_type){
    for(var i = self.__events.length-1; i >= 0; i--){
      var e = self.__events[i];
      switch(typeof id_or_type){
      case "number":
        if(e.id === id_or_type) self.__events.splice(i,1);
        break
      case "string":
      case "object":
        if(e.type === id_or_type) self.__events.splice(i,1);
        break
      }
    }
  };

};

if(typeof module !== "undefined" && typeof module.exports !== "undefined"){
  module.exports = EventEmitter;

} else if (typeof define === "function" && define.amd) {
  define([], function () { return EventEmitter; });
};
</script>

Setup

var e1 = new EventEmitter1(),
        e2 = new EventEmitter2({delimiter:'::', wildcard: true}),
        e3 = new EventEmitter(),
        e4 = new ShokaiEventEmitter(),
        cb1 = function() {
        var all = '';
        for (var i = 0, len = arguments.length; i < len; i++) {
          all += arguments[i]
        }
    
        return all + Math.random();
        };
    
    e1.on('data', cb1);
    e1.on('data', cb1);
    e1.on('data', cb1);
    e1.on('data', cb1);
    e1.on('data', cb1);
    
    e2.on('data::test::test::**::test', cb1);
    e2.on('data::test::test::**::test', cb1);
    e2.on('data::test::test::**::test', cb1);
    e2.on('data::test::test::**::test', cb1);
    e2.on('data::test::test::**::test', cb1);
    
    e3.on('data', cb1);
    e3.on('data', cb1);
    e3.on('data', cb1);
    e3.on('data', cb1);
    e3.on('data', cb1);
    
    e4.on('data', cb1);
    e4.on('data', cb1);
    e4.on('data', cb1);
    e4.on('data', cb1);
    e4.on('data', cb1);

Test runner

Ready to run.

Testing in
TestOps/sec
Joyent's EventEmitter, 1arg
e1.emit('data', 'yes');
ready
EventEmitter4, 1arg
e3.emitEvent('data', ['yes']);
ready
Joyent's EventEmitter, 5arg
e1.emit('data', 'yes', 'yes', 'yes', 'yes', 'yes');
ready
EventEmitter4, 5arg
e3.emitEvent('data', ['yes', 'yes', 'yes', 'yes', 'yes']);
ready
Joyent's EventEmitter, another case
e1.on('data', cb1);
e1.emit('data');
e1.removeListener('data', cb1);
ready
EventEmitter4, another case
e3.addEvent('data', cb1).emitEvent('data').removeEvent('data', cb1);
ready
EventEmitter2, 1arg
e2.emit('data::test::test::abcd::test', 'yes');
ready
EventEmitter2, 3arg
e2.emit('data', 'yes', 'yes', 'yes', 'yes', 'yes');
ready
EventEmitter2, another case
e2.on('data', cb1);
e2.emit('data');
e2.removeListener('data', cb1);
ready
Shokai's EventEmitter
e4.emit('data', 'yes');
ready

Revisions

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