js 基础操作性能开销测试,结果令人震惊

下面是测试例子可以直接在nodejs上跑

一些预料之外结果是

  1. 访问 get set 性能开销是访问普通属性 5倍左右, 比普通的函数调用还慢
  2. map get元素性能开销有时快有时慢不稳定,与元素数量有关
  3. 使用 object[key] 的方式访问 属性性能开销是object.var的2倍左右, 估计与元素数量有关
  4. index+=1 比 index++速度快
  5. 矩形包围盒判断性能开销主要是在属性访问上, if 判断性能开销几乎忽略不计
  6. map key用 string与number类型性能最好
  7. for of 的写法比普通的循环慢2倍左右
    性能瓶颈貌似在内存访问速度上

测试结果在备注里

function testMapPerformance(numElements) {

    // 创建一个新的Map  

    const myMap = new Map();

    const emyptyMap = new Map();

    let int_key = 23;

    let float_key = 23.1;

    let str_key = 'my_key';

    let symbol_key = Symbol();

    let arr = []

    const mcv = {

        mcdksc: false,

        mcv: { a:{b:{c:{d:{e:{}}}}},x:23.13,},

        get get_this_mcv() { return this.mcv; },

        x: 2031.34, y: 2491.201/3.3, w: 200.3, h: 423.1

    };

    // 向Map中添加元素  

    for (var i = 0; i < numElements; i++) {

        myMap.set(Symbol(), Symbol());

        arr.push(Symbol());

        // mcv[i] = 1;

    }

    myMap.set(int_key, 1);

    myMap.set(float_key, 1);

    myMap.set(str_key, 1);

    myMap.set(symbol_key, 1);

   

    let outside = 1

    const obj = {id : "mcv"}

    const keytest = 'mcv';

    function tst() { return  mcv.mcv; }

    function EmptyFunc(i) {outside = i}

    function forFunc(){

        for (let i = 0; i < 1; i++) {

            outside = i

        }

    }

    // 递归调用函数  

    let index = 0,len = arr.length

    function callFunc(){

        index++;

        if(index < len){

            callFunc();

        }

    }

    let testNum = 2;

    let pool = new Array(len);

    let offset = 1;

    function poolPut(v){

        pool[offset] = v;

        offset++;

    }

    function poolPool(){

        if(offset == 0){

            return;

        }

        offset--;

        return pool[offset];

    }

    poolPut({a:1, b:2})

    // 记录开始时间  

    let startTime = performance.now();

    // callFunc() // 8.145099999999997毫秒

    // forFunc()

    for (; index < len; index+=1) {

        // 运行测试代码  空循环耗时: 0.7192999999999969毫秒

        // let m = mcv[index]; // 1.0782999992370605毫秒

        // let m = arr[index] // 1.091599941253662毫秒

        // let m = pool[index] // 1.091599941253662毫秒

        // let m = mcv; // 1.0738999843597412毫秒

        // let m = mcv.mcv; // 1.3427000045776367毫秒

        // let m = mcv['mcv']; // 1.3657000064849854毫秒

        // let m = mcv[keytest]; // 1.5729000568389893毫秒

        // let m = mcv[obj.id]; // 1.7720000743865967毫秒

        // let m = mcv.mcv.a.b.c.d.e; // 1.7039000988006592毫秒

        // let m = tst(); // 1.8094000816345215毫秒

        // let m = tst().a.b.c.d.e; // 1.9147999286651611毫秒

        // let m = mcv.get_this_mcv; // 2.73009991645813毫秒

        // let m2 = myMap.get(index); // 4.779200000000003毫秒

        // let m2 = myMap.get(str_key);  // 1.370199999999997毫秒

        // let m2 = myMap.get(int_key); // 1.3554999999999993毫秒

        // let m2 = myMap.get(float_key);  // 1.7979999999999947毫秒

        // let m2 = myMap.get(symbol_key);  // 1.7554999999999978毫秒

        // let m2 = emyptyMap.get(index); // 1.3554999999999993毫秒

        // mcv[index] = 1; // 3.0701000000000036毫秒

        // delete mcv[index]; // 31.150600000000004毫秒

        // let isRect = mcv.x > 200 && mcv.y > 2000 && mcv.x + mcv.w < 200 && mcv.y +mcv.h <2001; // 2.4273000000000025毫秒

        // let isRect = testNum > index || testNum < index || testNum > len || index >= len; // 1.1011999999999986毫秒

        // mcv.x = index; // 1.2391000000000005毫秒

        // 总耗时 6.143899999999995毫秒

        // let col = ( mcv.x /  mcv.w) | 0;  // 3.6865999999999985毫秒

        // let row = (( mcv.y -  mcv.h) / mcv.h) | 0; // 4.951900000000002毫秒

        // let gid =  mcv.x * index + index; // 2.695599999999999毫秒

        // let airId = arr[index]; // 1.5437999999999903毫秒

        // let airId2 = arr[index]; // 1.5437999999999903毫秒

        // testNum+1 * 2 + 2 -3; // 0.9227999999999952毫秒

        // const m = Math.pow(10 , 4) // 1.1916999999999973毫秒

        // forFunc(); // 1.2959999999999994毫秒

        // EmptyFunc(); // 0.9909999999999997毫秒

        // const m = Math.round(i*1000) / 1000

        // const m = pool.pop(); // 1.3101999999999947毫秒

        // pool.push(mcv); // 2.5149999999999935毫秒

        // const m = poolPool(); // 1.2903999999999982毫秒

        // poolPut(mcv); // 2.118399999999994毫秒

        // const m = {} // 1.6015999999999977毫秒

        // const m = {b:2,a:1} // 1.6015999999999977毫秒

        // const m = {}; m.b=2; m.a=1; // 1.6毫秒

        // const m = []; // 1.4691000000000045毫秒

        // let m = 1; m ++; // 0.9345999999999961毫秒

        // outside++; // 1.0360999999999976毫秒

        // outside+=1; // 0.9040000000000035毫秒

        // outside+=index; // 1.2355000000000018毫秒

       

    }

    // myMap.forEach((value, key)=>{

    // })

    // 记录结束时间  

    const endTime = performance.now();

    // 计算并打印耗时  

    console.log(`操作耗时:${endTime - startTime}毫秒`, outside);

}

// 10w次循环耗时测试

testMapPerformance(100000)
3赞

同等数量for循环又比 object[key]的性能开销更高

千万别让面试官看到 :rofl:

如果面试官问这个可以直接跑路了 :rofl:没有比这个更八股文的东西了,虽然确实很有用

都JS了 还玩什么性能啊 ,你骑的就是个自行车 非要说怎么样能骑快 ,想快你换个摩托或者汽车不行吗

3赞

JS 性能优化,有时候的确挺玄学的。之前同事碰到过为某个类额外添加了一个装饰器修饰,结果导致整体性能大幅下降的情况,排查起来极其困难,最终还是苦力地 2 分法去排除。

那说明你还没遇到过需要极端优化性能的情况
比如前后端要用同一套战斗逻辑代码时, 为了节省服务端性能开销不得不优化

当前后端也可以换个语言写, 但维护成本摆在那

引擎组件公开的属性都是用get set实现的,会不会有性能问题
我自己测试 get set 性能开销感觉挺大

get / set 毕竟是多了一层的 function 调用,会比直接访问对应的属性慢点。
之前数学库的某些类型想改成 TypedArray 的优化,但是为了保持兼容性,封装了 get set,测下来性能影响还是挺大的。

建议如果是非常频繁大量的访问一些 get / set,而这些 get / set 的实现又只是单纯地获取属性或者对属性赋值,那么你直接访问私有变量验证看看性能是否有变化。毕竟我们现在都得考虑私有变量的兼容性了嘛。

另外,性能测试,在不同的 v8 版本上可能也会出现不同的结果。毕竟 v8 不同的大版本会针对性地做一些优化,有可能在低版本中跑分比较低的测试例,在高版本 v8 中有可能就得到了优化。

1赞

项目和开发有一个能跑就行

大佬,这种是什么情况呢?我们项目用到的装饰器也蛮多的,担心会不会对性能造成影响

担心也没用,你该用还得用,只能优化到项目能合格上线的地步就行了

收藏,好贴

自定义的装饰器尽量避免在运行过程中频繁动态添加、删除属性。因为添加删除属性会更改对象的的 Hidden Class,导致 JIT 重新优化或者退化。

你这个只测了js的,还没测jni的

感谢大佬分享,学习了~

原生交互频率比较低, 高一点也没啥

取决于babel编译后生成的是什么。。。

每帧都有原生交互,新渲染全都是c++