EventEmitter против RxJS против кефира
Я хотел сравнить производительность 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
Как вы, ребята, можете видеть, что кефир оказался самым медленным вопреки заявлению, сделанному по этой ссылке.
- Я сделал что-то не так при написании теста?
- Было бы здорово, если бы кто-нибудь мог объяснить разницу, почему это происходит. Особенно, когда вы сравниваете его с Javascript Event-Emitter.
1 ответ
Я знаю, что вы задавали этот вопрос некоторое время назад, но я думаю, что было бы полезно дать будущим читателям представление о некоторых проблемах, которые я заметил, глядя на тесты.
Во-первых, count
это не число в кефире, это поток. Попробуйте войти count
после звонка scan
, И главное, похоже, кефир count
Поток никогда не активируется, и вычисления никогда не запускаются! Это должно быть исправлено в первую очередь. Я подозреваю, что то же самое верно для эталонного теста Rx, но вам придется проверить документацию, чтобы увидеть, заблокирует ли создание потока из наблюдаемой ES или нет.
Я считаю, что вы можете реализовать асинхронный тест с этой библиотекой с чем-то похожим (непроверенный код):
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})
Имея это в виду, очень странно, что тест на кефире самый медленный, потому что он не работает. Я подозреваю, что тестовые массивы слишком малы и вычисляются слишком быстро, чтобы получить ценный тест. Скорее всего, тесты на самом деле измеряют время создания потока или в зависимости от того, какой из тестов содержит наибольшее количество мусора / процессов во время выполнения. Фактически, если тест Rx не ожидает завершения теста, он будет выполнять его обработку / очистку от мусора В ТЕЧЕНИЕ теста на кефир! Вы можете смягчить это, подождав, пока тесты Kefir и Rx завершатся в тесте; создать меньше мусора, повторно используя общий глобальный тестовый массив между тестами; и использовать очень, очень большой массив, чтобы убедиться, что итерация - это то, что доминирует во времени, проведенном в тестах.
Наконец, для асинхронных эталонных тестов (Kefir и Rx) необходимо убедиться, что эталонные тесты обрабатывают события одинаково по отношению к циклу событий. Я уверен, что пример Kefir обрабатывает каждое событие в РАЗЛИЧНОМ тике цикла событий и должен будет ждать выполнения любой другой активности браузера (рендеринг / рисование, другие обратные вызовы / тайм-ауты и т. Д.) Между каждым шагом в поток. Рассмотрим вывод следующего кода:
console.log('a')
setTimeout(function() {
console.log('b')
}, 0);
console.log('c')
Этот код будет всегда печатать a
, c
, b
с небольшой ненулевой задержкой в печати финала b
,
Я считаю, что вы можете заставить кефир обрабатывать массив в одном тике с чем-то вроде Kefir.constant([1,2,3...10]).flatten()
, Однако я не думаю, что очень полезно сравнивать две потоковые инфраструктуры в синхронном тесте, потому что это не их предназначение.
Наконец, scan
операция семантически отличается от forEach
/ do
в других средах, потому что он создает значение для выходного потока на каждом шаге для любых потенциальных слушателей, в то время как другие должны только выполнить этот один блок кода.
Эталонные тесты получить очень сложно, но я надеюсь, что это поможет.