baidu / san

A fast, portable, flexible JavaScript component framework
https://baidu.github.io/san/
MIT License
4.73k stars 551 forks source link

[讨论] 枚举变量对比优化成位运算 #439

Closed LeuisKen closed 5 years ago

LeuisKen commented 5 years ago

之前和 @errorrik 讨论过这个问题,是否将枚举的判等从 === 优化成 &

当时的结论是:

不过最近看到,angular 和 react 的源码中都有这种应用了。

JavaScript社区中对这种玩法总结了如下优点: image

参考:https://youtu.be/_VHNTC67NR8

所以我们是否也可以考虑引入这个写法呢?

errorrik commented 5 years ago

你是说,在模板语法里加入这种支持,还是san的实现里这么玩?

LeuisKen commented 5 years ago

https://github.com/baidu/san/blob/192bf81846eca56d1432290461121ba1b3729e74/src/view/compile-expr-source.js#L49

类似这种地方,从 path.type === ExprType.ACCESSOR 变为 path.type & ExprType.ACCESSOR

errorrik commented 5 years ago

那是可以的,其实只是我不太习惯

errorrik commented 5 years ago

怎么,你要pr吗

LeuisKen commented 5 years ago

怎么,你要pr吗

都行,看看有没有新同学有兴趣?

errorrik commented 5 years ago

都行,看看有没有新同学有兴趣?

这个,应该是没有。

qianliu013 commented 5 years ago

这个修改,我来做吧。 由于不够熟悉源码,可能需要一段时间才能提交 PR。

qianliu013 commented 5 years ago

关于枚举变量优化成位运算这种做法,我想了想,不太赞同。

在 san 的源码搜索 ExprType,用于判断的场合就两种,相等比较与在 switch case 中使用。 switch case 也算相等比较,但是也会有多种 case 对应一种解决方案的地方。 如果要优化成位运算的方式,而不改 switch case,则在我看来,没有必要,因为最终的代码修改,只是将 === 变成了 &。 如果把 switch case 改成多层 if,则更只是为了性能;还不如直接用哈希 map 来映射,会快很多。

位运算的优化主要就是为了集合(Set)服务的,要有 |& 两种操作。 react 的副作用类型可以重叠的,angular 的 TNodeFlags 也可以重叠。 对于 san,枚举变量 Expr,从抽象意义上而言,应该是不会重叠的(除非增加别的抽象层)。如果把 === 改成了 &,代码意义相当于从类型相同变成了类型集合是否有交集,不太适合目前 Expr 类型的使用方式。

综上,我觉得暂时没有必要改成位运算的方式。

LeuisKen commented 5 years ago

嗯 我也认同你的观点 是否可以做个测试比对一下 看看 === 与 & 的性能能差到多少?我多少有点在意这个收益,另外我也没有这相关的量化数据。

qianliu013 commented 5 years ago

测试结果大致是:

const a0 = 0;
const a1 = 1;
const a2 = 2;
const a3 = 3;
const a4 = 4;
const a5 = 5;
const a6 = 6;

const b0 = 1<<0;
const b1 = 1<<1;
const b2 = 1<<2;
const b3 = 1<<3;
const b4 = 1<<4;
const b5 = 1<<5;
const b6 = 1<<6;

function ifElseFn(n) {
    if(n === a0) return a0;
    if(n === a1) return a1;
    if(n === a2) return a2;
    if(n === a3) return a3;
    if(n === a4 || n === a5) return a5;
    if(n === a6) return a6;
    return -1;
}

function bIfElseFn(n) {
    if(n & b0) return b0;
    if(n & b1) return b1;
    if(n & b2) return b2;
    if(n & b3) return b3;
    if(n & (b4 | b5)) return b5;
    if(n & b6) return b6;
    return -1;
}

function simpleIfElse(n) {
    if(n === a3) {
        return a3
    }
    return n
}

function bSimpleIfElse(n) {
    if(n & b3) {
        return b3
    }
    return n
}

const execute = fn => fn(Math.floor(Math.random() * 7))

const aMap = { 0: a0, 1: a1, 2: a2, 3: a3, 4: a4, 5: a5, 6: a6 }
const bMap = { 0: b0, 1: b1, 2: b2, 3: b3, 4: b4, 5: b5, 6: b6 }

// const Benchmark = require('benchmark')
const suite = new Benchmark.Suite;

suite
    .add('if-else', () => execute(num => ifElseFn(aMap[num])))
    .add('bit-if-else', () => execute(num => bIfElseFn(bMap[num])) )
    .add('simple-if-else', () => execute(num => simpleIfElse(aMap[num])))
    .add('bit-simple-if-else', () => execute(num => bSimpleIfElse(bMap[num])))
    .on('cycle', event => {
        console.log(String(event.target));
    })
    .on('complete', function complete() {
        console.log(`Fastest is ${this.filter('fastest').map('name')}`);
    })
    .run({ 'async': true });
if-else x 32,846,167 ops/sec ±0.56% (59 runs sampled)
bit-if-else x 34,814,547 ops/sec ±1.13% (59 runs sampled)
simple-if-else x 83,446,393 ops/sec ±4.00% (56 runs sampled)
bit-simple-if-else x 75,476,747 ops/sec ±5.44% (50 runs sampled)
Fastest is simple-if-else
# 测试结果来自 Chrome 77
LeuisKen commented 5 years ago

ok,这么看的话性能收益也不够。

errorrik commented 5 years ago

非常细致的工作,赞 @qianliu013 @LeuisKen

Dafrok commented 5 years ago

高级