Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance Cost of PubSub / Excessive Events and Event Handlers in JavaScript?

Pub Sub / Event Driven architecture is a common practice in the world of Client & Server side JavaScript. I have been tasked to architect a very large Web Application using Dojo as Front End and node.js as backend. Pub / Sub seems very appealing as it allows for a great deal of parallelism amongst teams. I am afraid though, if there will be performance consequences.

I have a general question regarding the cost of Events and Event Handlers in JavaScript. I've already seen this, this, this, even this and this. But I still don't seem to see a general purpose answer. Independent of frameworks, suppose we have 2 methods

publish() //Like jQuery's / Dojo's trigger(), EventEmitter's emit()

and

subscribe() //Like jQuery's / Dojo's / EventEmiter's / DOM's on() connect() live() addEventListener()

Problem 1: What is the cost of each Event trigger?

Case 1: Cleaner (Loosely Coupled) Code emphasizing Pub / Sub

object.publish('message1', data);
object.publish('message2', data);
...
object.publish('message100', data);

//All these are in separate files / modules    
subscribe (object, 'message1', function (data) { A()...})
subscribe (object, 'message2', function (data) { B()...})
subscribe (object, 'message100', function (data) { Z()...})

Case 2: Tightly Coupled code! But is it more performant?

data.message = 'message1'
object.publish('message', data)
subscribe (object, 'message', function (data) {
  switch (data) {
    case 'message1':
      A();
      break();    
    case 'message2':
      B();
      break();
     ...
    case 'message100':
      Z();
      break();
  }
})

Problem 2: What is the cost of each Event listener?

object.publish('event', data);

Case 1: Again, Cleaner (Loosely Coupled) Code emphasizing Pub / Sub

//A.js    
subscribe (object, 'event', function (data) {
   A();
});

//B.js
subscribe (object, 'event', function (data) {
   B();
});

//C.js
subscribe (object, 'event', function (data) {
   C();
});

Case 2: Again, Tightly Coupled code! But is it more performant?

subscribe (object, 'event', function (data) {
   A();
   B();
   C();
});

Q1: Can someone point me towards research and performance tests done for this in the Client Side (using DOMEvents or Custom Events), Server Side (EventEmitter and more in Node.js)? Its a simplistic example but can easily grow to 1000's of such calls as the app is pretty large. If not, how do I go about benchmarking myself for noticeable performance degradation? Maybe with something like jsperf? Any theoretical foundation to know hwy one is more performant than the other?

Q2: If Case 1s are more performant, what is the best way to write loosely coupled code? Any method of finding the middle ground? Writing the code like Case 1 but some intermediary compilation / build process to turn it to Case 2 (Something like what Google Closure compiler does in other perf cases?) say using [Esprima]. I hate to complicate the build process even more than it is. Is the performance boost (if any) worth all this?

Q3: Finally, although I'm looking for a very specific JavaScript specific answer here, it might help to know the performance costs in other languages / environments. The fact that in most cases events are hardware triggered (using the concept of interrupts) contribute anything to the answer?

Thanks to all who made it till the end of this Q!!! Much Appreciated!!!

like image 689
Gaurav Ramanan Avatar asked Dec 28 '14 15:12

Gaurav Ramanan


1 Answers

Each architecture decision does have a performance impact as you have suggested:


Callback (fastest)

one to one binding has a direct reference to the function

315,873 ops/sec

Pub-Sub Events

too many binding requires a loop to call multiple callbacks, more callbacks = worse performance

291,609 ops/sec

Promises (slowest)

use a delay to ensure each item in the chain is not invoked at the same time, more chains = worse performance

253,301 ops/sec


I would choose callbacks for most cases unless you have a load of interconnected modules, then pub/sub is useful.

And remember these can work in tandem with each other. I usually allow a callback and a pub/sub event within the same module, letting the developer choose which one they would prefer to use:

var module = {
    events: [],
    item: { 'name': 'Test module' },
    updateName: function(value, callback) {
        this.item.name = value;
        if (callback) {
            callback(this.item);
        }
        this.dispatchEvent('update', this.item);
    },
    addEvent: function(name, callback) {
        if (!this.events[name]) { this.events[name] = []; }
        this.events[name].push(callback);
    },
    removeEvent: function(name, callback) {
        if (this.events[name]) {
            if (callback) {
                for (var i=0; i<this.events[name].length; ++i) {
                  if (this.events[name][i] === callback) { this.events[name].splice(i, 1); return true; }
                }
            }
            else { delete this.events[name]; }
        }
    },
    dispatchEvent: function(name, data) {
        if (this.events[name]) {
            for (var i=0; i<this.events[name].length; ++i) {
                this.events[name][i]({ data: data, target: this, type: name});
            }
        }
    }
};

module.updateName('Dan'); // no callback, no event
module.updateName('Ted', function (item) { console.log(item); }); // callback, no event
module.addEvent('update', function (item) { console.log(item); }); // add an event
module.updateName('Sal'); // no callback, event callback
module.addEvent('update', function (item) { console.log('event2', item); }); // add a second event
module.updateName('Jim'); // no callback, two event callbacks
like image 84
Kim T Avatar answered Nov 03 '22 03:11

Kim T