代碼最小的庫rx4rx-lite
雖然在性能測試中超過了callbag,但和most庫較量的時候卻落敗了狸棍,于是我下載了most庫身害,要解開most庫性能高的原因。
我們先上一組測試數(shù)據(jù)草戈,這是在我的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) |
經(jīng)過我的不懈努力終于把性能超過了most庫塌鸯。
我先介紹一下fast庫的工作原理,下一篇文章我再介紹如何從most庫中找到性能提升的要領(lǐng)唐片。
在fast庫中丙猬,我們開始使用一個基類作為一切操作符的父類,名為Sink费韭。
class Sink {
constructor(sink, ...args) {
this.defers = new Set()//用于存放需要釋放的操作
this.sink = sink
this.init(...args)
if (sink) sink.defers.add(this)//用于釋放的連鎖反應(yīng)
}
init() {
}
//是否連鎖釋放
set disposePass(value) {
if (!this.sink) return
if (value)
this.sink.defers.add(this)
else this.sink.defers.delete(this)
}
//數(shù)據(jù)向下傳遞
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函數(shù)揽思,現(xiàn)在變?yōu)閭魅雜ink對象袜腥,這里十分類似向Observable傳入Observer對象。但是與rxjs不同的是,我們的Observable仍然是一個函數(shù)羹令,我們看一個從數(shù)組構(gòu)造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
這是常規(guī)做法,這里把變量定義到了對象的屬性上面福侈,純粹是為了提高一點點性能酒来,經(jīng)過測試發(fā)現(xiàn),直接訪問(讀寫操作)局部變量肪凛,比訪問對象的屬性要慢一些堰汉。
由于大部分的操作符都是相同的調(diào)用方式,所以可以抽象成一個函數(shù)
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庫的基本構(gòu)建邏輯已經(jīng)展示完畢伟墙。
至于為什么這么快翘鸭,就請聽下回分解。
(未完待續(xù))