代码最小的库rx4rx-lite
虽然在性能测试中超过了callbag,但和most库较量的时候却落败了,于是我下载了most库,要解开most库性能高的原因。 我们先上一组测试数据,这是在我的windows10 上面跑的
dataflow for 1000000 source events
lib
op/s
samples
rx4rx-lite
11.29 op/s ± 1.47%
(56 samples)
rx4rx-fast
22.56 op/s ± 1.77%
(57 samples)
cb-basics
9.56 op/s ± 1.73%
(49 samples)
xstream
5.37 op/s ± 0.68%
(30 samples)
most
17.32 op/s ± 1.93%
(82 samples)
rx 6
6.28 op/s ± 3.10%
(35 samples)
经过我的不懈努力终于把性能超过了most库。 我先介绍一下fast库的工作原理,下一篇文章我再介绍如何从most库中找到性能提升的要领。
在fast库中,我们开始使用一个基类作为一切操作符的父类,名为Sink。
class Sink {
constructor(sink, ...args) {
this.defers = new Set()//用于存放需要释放的操作
this.sink = sink
this.init(...args)
if (sink) sink.defers.add(this)//用于释放的连锁反应
}
init() {
}
//是否连锁释放
set disposePass(value) {
if (!this.sink) return
if (value)
this.sink.defers.add(this)
else this.sink.defers.delete(this)
}
//数据向下传递
next(data) {
this.sink && this.sink.next(data)
}
//完成/error事件向下传递
complete(err) {
this.sink && this.sink.complete(err)
this.dispose(false)
}
error(err) {
this.complete(err)
}
//释放即取消订阅功能
dispose(defer = true) {
this.disposed = true
this.complete = noop
this.next = noop
this.dispose = noop
this.subscribes = this.subscribe = noop
defer && this.defer() //销毁时终止事件源
}
defer(add) {
if (add) {
this.defers.add(add)
} else {
this.defers.forEach(defer => {
switch (true) {
case defer.dispose != void 0:
defer.dispose()
break;
case typeof defer == 'function':
defer()
break
case defer.length > 0:
let [f, thisArg, ...args] = defer
if (f.call)
f.call(thisArg, ...args)
else f(...args)
break
}
})
this.defers.clear()
}
}
subscribe(source) {
source(this)
return this
}
subscribes(sources) {
sources.forEach(source => source(this))
}
}
为了性能,代码量稍微有点多了。原本传入next和complete函数,现在变为传入sink对象,这里十分类似向Observable传入Observer对象。但是与rxjs不同的是,我们的Observable仍然是一个函数,我们看一个从数组构造Observable的代码
exports.fromArray = array => sink => {
sink.pos = 0
const l = array.length
while (sink.pos < l && !sink.disposed)
sink.next(array[sink.pos++])
sink.complete()
}
这个pos为什么不直接定义一个变量呢?let pos = 0
这是常规做法,这里把变量定义到了对象的属性上面,纯粹是为了提高一点点性能,经过测试发现,直接访问(读写操作)局部变量,比访问对象的属性要慢一些。
由于大部分的操作符都是相同的调用方式,所以可以抽象成一个函数
exports.deliver = Class => (...args) => source => sink => source(new Class(sink, ...args))
take操作符就变成了这样
class Take extends Sink {
init(count) {
this.count = count
}
next(data) {
this.sink.next(data)
if (--this.count === 0) {
this.defer()
this.complete()
}
}
}
exports.take = deliver(Take)
而我们的subscriber就变成了这样
exports.subscribe = (n, e = noop, c = noop) => source => {
const sink = new Sink()
sink.next = n
sink.complete = err => err ? e(err) : c()
source(sink)
return sink
}
至此fast库的基本构建逻辑已经展示完毕。 至于为什么这么快,就请听下回分解。 (未完待续)