26

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!!!

Community
  • 1
  • 1
Gaurav Ramanan
  • 3,655
  • 2
  • 21
  • 29
  • You can write your own tests and see for yourself, http://jsperf.com/ – nicholaswmin Dec 29 '14 at 22:07
  • 1
    @NicholasKyriakides The point isn't just about writing the tests (though I have mentioned I need help on that as well), its also about a theoretical foundation as to why one is more performant than the other and what is the best way to find the balance between performance and code cleanliness. – Gaurav Ramanan Jan 06 '15 at 14:31
  • That can and will change with every re-implementation. You need to test because one may be more performant in V8 (chrome/node.js) while the other works better in FF/IE/whatever. I'm not at all sure its possible to separate theory and practice here. And I suspect the answer to your particular case like most is that javascript perf won't be your bottleneck either way and that pub/sub will likely be much cleaner to build/maintain. – Jared Smith Jan 06 '15 at 14:45
  • @JaredSmith I think events are the very basic foundation in JS and I really feel it is such a core language part that implementations may not differ as much as we think. Well, at least performances in V8 to be consistent. – Gaurav Ramanan Jan 06 '15 at 14:47
  • 1
    You're probably right, but as far as perf goes you're *almost* always better off worrying about, just to name a few: CSS animations, http cache headers, image optimizations, gratuitous ajax, blocking, layout triggering changes, paint triggering changes, etc. All of those things are vastly more important for performance in the browser than js. For node, its almost certainly going to be running on your server where if for some reason its not fast enough you can more than likely (and cheaply, compared to dev time) throw hardware at it. Compared that to a maintenance nightmare. – Jared Smith Jan 06 '15 at 14:56
  • 1
    I don't see a big difference in the two ways. Somewhere, you are switching based on a String. Whether it is in an `EventEmitter` or whether it is after you receive the event, something is switching on the event name. The real trick would be to eliminate switching on a String -- callbacks are a way, but not the way the other answer uses them. In theory, I would think a number-based message system would be faster. Instead of `on(messageType: String, ...)` implement `on(messageId: Number, ...)`. Then you could use `messageId` as an offset into a an array of callbacks: `callbacks[messageId]`. – David Griffin Mar 22 '16 at 18:33

1 Answers1

12

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
Reza Shoja
  • 887
  • 2
  • 11
  • 24
Kim T
  • 5,770
  • 1
  • 52
  • 79
  • 1
    Thanks for your answer. It sheds a new dimension with application structure with callbacks. However my basic question regarding performance still remains. It would be nice to get some benchmark numbers backed by some theoretical proof of the same. – Gaurav Ramanan Jan 22 '15 at 17:08
  • 1
    I've created an example on js perf which illustrated the first two approaches: http://jsperf.com/callback-vs-pub-sub in most browsers the callback is twice as fast, and pub sub decreases in performance as you add more callbacks – Kim T Jan 22 '15 at 22:57
  • 1
    Added promises example to this link: https://jsperf.com/callback-vs-pubsub-vs-promise – Kim T Jan 05 '18 at 19:29