0

我想比较 JS EventEmitter 和 RxJS 的性能。我编写了以下基准脚本来做到这一点:

性能测试

import Rx from 'rxjs/Rx';
import Kefir from 'kefir';

import { EventEmitter } from "events";

let Benchmark = require ("benchmark");
let suite = new Benchmark.Suite;

suite
.add('for', () => {
  let numArray = [1,2,3,4,5,6,7,8,9,10];
  let count = 0;
  for (let i = 0; i<numArray.length; i++)
    count += numArray[i];
})
.add('forEach', () => {
  let numArray = [1,2,3,4,5,6,7,8,9,10];
  let count = 0;
  numArray.forEach((num) => { count += num; });
})
.add('eventEmitter', () => {
  let numArray = [1,2,3,4,5,6,7,8,9,10];
  let count = 0;
  let myEmitter = new EventEmitter();
  myEmitter.on('number', (num) => { count += num; });
  numArray.forEach((num) => { myEmitter.emit('number', num); });
})
.add('rxjs', () => {
  let numArray = [1,2,3,4,5,6,7,8,9,10];
  let count = 0;
  let source = Rx.Observable.from(numArray)
    .do((x) => { count += x }, (error) => {}, () => {});
  source.subscribe((x) => {}, (error) => {}, () => {});
})
.add('kefir', () => {
  let numArray = [1,2,3,4,5,6,7,8,9,10];
  let count = 0;
  let stream = Kefir.sequentially(0, numArray);
  count = stream.scan(sum => sum + 1, 0);
})
.on('cycle', function (event) {
  console.log(String(event.target));
})
.on('complete', function () {
  console.log('Slowest is ' + this.filter('slowest').map('name'));
})
.run({'async': true});

性能结果

for x 47,595,916 ops/sec ±1.58% (87 runs sampled)
forEach x 4,428,485 ops/sec ±0.75% (86 runs sampled)
eventEmitter x 1,478,876 ops/sec ±0.61% (86 runs sampled)
rxjs x 547,732 ops/sec ±0.66% (86 runs sampled)
kefir x 496,709 ops/sec ±5.15% (50 runs sampled)
Slowest is kefir

如您所见,与此链接上的声明相反,Kefir 的速度最慢。

  1. 我在写测试时做错了吗?
  2. 如果有人能解释为什么会发生这种差异,那就太好了。特别是当您将它与 javascript 事件发射器进行比较时。
4

1 回答 1

1

我知道你不久前问过这个问题,但我认为让未来的读者了解我在查看基准时注意到的一些问题可能会有所帮助。

首先,countKefir 中的不是数字,而是流。count打电话后尝试登录scan。重要的是,似乎 Kefircount流从未被激活,计算也从未运行!这需要先解决。我怀疑 Rx 基准测试也是如此,但您必须检查文档以查看从 ES observable 创建流是否会阻塞。

我相信您可以使用类似于(未经测试的代码)的库来实现异步测试:

suite.add('My async test', function(deferred) {
    let numArray = [1,2,3,4,5,6,7,8,9,10];
    let stream = Kefir.sequentially(0, numArray);
    let countStream = stream.scan(sum => sum + 1, 0);
    countStream.onEnd(() => deferred.resolve());
}, {'defer': true})

考虑到这一点,很奇怪 Kefir 的基准测试是最慢的,因为它不起作用。我怀疑测试数组太小并且计算太快而无法获得有价值的基准。很可能测试实际上是在测量流构建时间,或者在运行时运行时恰好有最多垃圾收集/处理的基准测试。事实上,如果 Rx 基准测试不等待测试完成,它将在 Kefir 测试期间进行处理/垃圾清理!您可以通过等待 Kefir 和 Rx 测试在基准测试中完成来缓解这种情况;通过在基准测试之间重用通用的全局测试数组来减少垃圾;并使用一个非常非常大的数组来确保迭代是在基准测试中花费的主要时间。

最后,对于异步基准测试(Kefir 和 Rx),您需要确保基准测试以与事件循环相同的方式处理事件。我确定 Kefir 示例在事件循环的不同滴答声中处理每个事件,并且必须等待浏览器的任何其他活动(渲染/绘画、其他回调/超时等)在每个步骤之间完成溪流。考虑以下代码的输出:

console.log('a')
setTimeout(function() {
    console.log('b')
}, 0);
console.log('c')

此代码将始终打印a, cb并在打印最终的 . 时具有较小的非零延迟b

我相信您可以让 Kefir 在同一个刻度中处理数组,例如Kefir.constant([1,2,3...10]).flatten(). 但是,我认为在同步基准测试中比较两个流框架并不是很有用,因为这不是它们的预期目的。

最后,该scan操作在语义上与其他框架中的forEach/不同do,因为它在每个步骤中为任何潜在侦听器的输出流生成一个值,而其他操作只需要运行该一个代码块。

基准很难正确设置,但我希望这会有所帮助。

于 2017-08-03T21:51:36.230 回答