smallnewer / bugs

18 stars 4 forks source link

V8对象属性访问性能测试 #129

Open smallnewer opened 6 years ago

smallnewer commented 6 years ago

最近在追JS的性能问题,踩到了一个坑,这个坑着实让我非常意外;而且项目里90%的性能坑都与此有关,也不知道是幸运还是不幸运。这个坑就是:当用对象[变量]的形式,我当初知道这个最差会变成一次map查询,并没在意,但是没想到会慢到150ns/per。 接下来的测试能大概复现这个问题。

var obj1 = {a:1,b:1}
function test1 () {
    var key = 'a'
    var key2 ='b'
    var st = Date.now();
    // 函数保持单态可以被良好的JIT优化,而多态导致优化失败,运行期间会进行更耗时的lookup。
    // 这里变化参数顺序,是为了复现多态。
    for (var i = 0; i < 10000000/2; i++) {
        getByKEY(obj1, key, key2)
    };
    for (var i = 0; i < 10000000/2; i++) {
        getByKEY(obj1, key2, key)
    };
    console.log('test1', (Date.now() - st) * 1000000 / 10000000, 'ns')
}

function test2 () {
    var st = Date.now();
    for (var i = 0; i < 10000000; i++) {
        getByLiteral(obj1)
    };
    console.log('test2', (Date.now() - st) * 1000000 / 10000000, 'ns')
}
function getByKEY (obj1, key, key2) {
    return obj1[key] + obj1[key2]
}
function getByLiteral (obj1) {
    return obj1.a + obj1.b
}

test1(); // 15ns
test1();
test1();
test1();
test1();
test2();  // 1ns
test2();
test2();
test2();
test2();
test2();

测试里,一次慢查询是15ns,但在实际项目中是150ns~300ns,而使用字面量直接访问只有1ns。我不打算深究下去了(但深度怀疑两点,多个Isolate在单核里比Node慢;项目中内存使用情况复杂,有其他开销;),个人觉得这不是设计上的问题,恰好在设计上还留有余地给优化。许多语言都会触发该问题,只是慢一点和慢许多的问题。

那么解决思路的话,主要是两点:

  1. 利用类似C++宏定义的方案,在启动时生成好使用字面量方案的函数,兼顾扩展性和性能。如果有的地方不可避免要lookup,比如要根据user_id查询一个玩家的数据,那就保证在逻辑实现时尽量少触发lookup。
  2. 任何时候都尽量保持单态,这点强类型语言如TypeScript有绝对优势。如果无法引入TS,那么项目进行约定和检查,保证性能热点函数的单态,实践中也足够了。

具体下来,在我们的引擎里,已经想到了许多优化方案,相信会非常👍。

smallnewer commented 6 years ago

关于V8里对象的内存布局,已经有许多文章解释过了,这里贴最近看到的。 https://segmentfault.com/a/1190000008188648 http://mrale.ph/blog/2014/07/30/constructor-vs-objectcreate.html