JavaScript 中 PubSub / 过多事件和事件处理程序的性能成本?

Posted

技术标签:

【中文标题】JavaScript 中 PubSub / 过多事件和事件处理程序的性能成本?【英文标题】:Performance Cost of PubSub / Excessive Events and Event Handlers in JavaScript? 【发布时间】:2015-02-24 23:30:37 【问题描述】:

Pub Sub / Event Driven architecture 是客户端和服务器端 javascript 领域的常见做法。我的任务是使用 Dojo 作为前端和 node.js 作为后端来构建一个非常大的 Web 应用程序。 Pub / Sub seems very appealing 因为它允许团队之间的大量并行性。不过,我担心会不会对性能产生影响。

我有一个关于 JavaScript 中事件和事件处理程序成本的一般性问题。我已经看过this、this、this,甚至this 和this。但我似乎仍然没有看到通用的答案。独立于框架,假设我们有2种方法

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

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

问题 1:每个事件触发器的成本是多少?

案例 1:强调 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()...)

案例 2:紧耦合代码!但它的性能更高吗?

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();
  
)

问题 2:每个事件监听器的成本是多少?

object.publish('event', data);

案例 1:再次强调 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();
);

案例 2:同样,紧耦合代码!但它的性能更高吗?

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

Q1:谁能指出我在客户端(使用 DOMEvents 或自定义事件)、服务器端(@ 987654331@ 和 Node.js 中的更多内容)?这是一个简单的示例,但由于应用程序很大,因此很容易增长到 1000 次此类调用。如果没有,我该如何对自己进行基准测试以发现明显的性能下降?也许像jsperf 这样的东西?有什么理论基础可以知道为什么一个比另一个性能更好?

Q2:如果案例 1 的性能更高,那么编写松散耦合代码的最佳方式是什么?有什么方法可以找到中间立场?编写类似 Case 1 的代码,但需要一些中间编译/构建过程将其转换为 Case 2(类似于 Google Closure compiler 在其他性能案例中所做的事情?)说使用 [埃斯普里马]。我讨厌使构建过程变得更加复杂。性能提升(如果有的话)值得这一切吗?

Q3:最后,虽然我在这里寻找一个非常具体的 JavaScript 特定答案,但了解其他语言/环境中的性能成本可能会有所帮助。在大多数情况下,事件是由硬件触发的(使用中断的概念)这一事实对答案有什么帮助?

感谢所有坚持到 Q 结束的人!!!非常感谢!!!

【问题讨论】:

您可以编写自己的测试并亲自查看,jsperf.com @NicholasKyriakides 重点不仅仅是编写测试(尽管我已经提到我也需要帮助),它还涉及到为什么一个比另一个性能更好的理论基础以及在性能和代码简洁性之间找到平衡的最佳方法是什么。 这可以而且将会随着每次重新实现而改变。您需要进行测试,因为一个在 V8 (chrome/node.js) 中可能性能更高,而另一个在 FF/IE/whatever 中效果更好。我完全不确定是否可以在这里将理论和实践分开。我怀疑你的特殊情况的答案就像大多数人一样,javascript perf 不会成为你的瓶颈,而且 pub/sub 可能会更清洁/维护。 您可能是对的,但就性能而言,您几乎总是最好不要担心,仅举几例:CSS 动画、http 缓存标头、图像优化、无偿 ajax、阻塞、布局触发更改、绘制触发更改等。所有这些对于浏览器中的性能来说都比 js 更重要。对于节点,它几乎肯定会在您的服务器上运行,如果由于某种原因它不够快,您很可能(并且与开发时间相比更便宜)向它扔硬件。与维护噩梦相比。 我认为这两种方式没有太大区别。在某处,您正在基于字符串进行切换。无论是在EventEmitter 中,还是在您收到事件之后,事件名称都在切换。真正的技巧是消除对字符串的切换——回调是一种方式,但不是其他答案使用它们的方式。理论上,我认为基于数字的消息系统会更快。而不是on(messageType: String, ...) 实现on(messageId: Number, ...)。然后您可以使用messageId 作为回调数组的偏移量:callbacks[messageId] 【参考方案1】:

正如您所建议的,每个架构决策都会对性能产生影响:


回调(最快)

一对一绑定直接引用函数

315,873 ops/sec

发布订阅事件

绑定过多需要循环调用多个回调,回调越多 = 性能越差

291,609 ops/sec

承诺(最慢)

使用延迟来确保链中的每个项目不会同时被调用,链越多 = 性能越差

253,301 ops/sec


我会在大多数情况下选择回调,除非你有大量相互连接的模块,那么 pub/sub 很有用。

请记住,它们可以相互协同工作。我通常允许在同一个模块中使用回调和 pub/sub 事件,让开发人员选择他们更喜欢使用哪一个:

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

【讨论】:

感谢您的回答。它通过带有回调的应用程序结构摆脱了一个新的维度。但是,我关于性能的基本问题仍然存在。获得一些由相同理论证明支持的基准数字会很好。 我在 js perf 上创建了一个示例,说明了前两种方法:jsperf.com/callback-vs-pub-sub 在大多数浏览器中,回调速度是原来的两倍,而 pub sub 的性能会随着您添加更多回调而降低跨度> 在此链接中添加了 Promise 示例:jsperf.com/callback-vs-pubsub-vs-promise

以上是关于JavaScript 中 PubSub / 过多事件和事件处理程序的性能成本?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以直接在javascript中调用Pubsub的订阅拉取而不是调用java方法来获取数据?

javascript PubSub / EventBus / Mediator JavaScript模式

javascript 活动PubSub

javascript pubsub - 缓存obj

javascript PubSub活动

JavaScript 优雅的Pubsub与jQuery