JavaScript函数定义性能

问题描述

关于功能定义(而非执行)的性能考虑的本质是什么?什么会导致性能下降,以及在现代js环境中倾向于有效处理哪些情况?

要考虑的一些比较是:

在循环中定义函数还是在循环外定义函数函数从不使用外部引用):

let cases = {
  defineFnInLoop: testArr => {
    for (let elem of testArr) {
      let fn = elem => elem;
      fn(elem);
    }
  },defineFnBeforeLoop: testArr => {
    let fn = elem => elem;
    for (let elem of testArr) fn(elem);
  }
};

// You can ignore (or scrutinize,if you prefer!) this test code:
(async (batches=50,runs=20*1000,sleepMs=50,constElems=100,elemsPerBatch=100) => {
  let timing = Object.fromEntries(Object.entries(cases).map(([ k,fn ]) => [ k,0 ]));
  let arr = [ ...new Array(constElems) ].map(v => null);
  for (let i = 0; i < batches; i++) {
    arr.push(...[ ...new Array(elemsPerBatch) ].map(v => null));
    // Interleaved testing of cases helps mitigate effect of cpu fluctuations
    for (let k in cases) {
      let fn = cases[k];
      // Timing sensitive segment; I've done my best to reduce the overhead:
      let ms = +new Date(); for (let j = 0; j < runs; j++) fn(arr); timing[k] += new Date() - ms;
    }
    console.log(`Finished batch ${i + 1} / ${batches} (arr size: ${arr.length})`);
    await new Promise(r => setTimeout(r,sleepMs));
  }
  let [ best,...others ] = Object.entries(timing).sort((v1,v2) => v1[1] - v2[1]);
  console.log(`Winner: ${best[0]} (${best[1]}ms)`);
  for (let [ name,ms ] of others)
    console.log(`Loser:  ${name} (${ms}ms; ${(100 * ms / best[1] - 100).toFixed()}% slower)`); 
})();

内部或外部定义函数,可能使用外部引用:

let cases = {
  useExternalRefInLoop: testArr => {
    let ref = null;
    for (let elem of testArr) {
      let fn = elem => (elem,ref);
      fn(elem);
    }
  },noExternalRefInLoop: testArr => {
    let ref = null;
    for (let elem of testArr) {
      let fn = elem => (elem,elem);
      fn(elem);
    }
  },useExternalRefOutLoop: testArr => {
    let ref = null;
    let fn = elem => (elem,ref);
    for (let elem of testArr) fn(elem);
  },noExternalRefOutLoop: testArr => {
    let ref = null;
    let fn = elem => (elem,elem);
    for (let elem of testArr) fn(elem);
  }
};

// You can ignore (or scrutinize,ms ] of others)
    console.log(`Loser:  ${name} (${ms}ms; ${(100 * ms / best[1] - 100).toFixed()}% slower)`); 
})();

我已经使用jsbench.me运行了相同的代码,结果相同:

两个比较的结果令人惊讶。对我而言,每次迭代定义一次函数比循环之前一次定义函数要快。另外,引用外部引用(非参数引用)的函数比仅引用其参数的函数看起来更快。当函数使用外部引用并在循环中重复定义时,我发现这尤其令人惊讶-我认为外部引用会降低解释器减轻重复函数定义的影响的能力。 我有信心useExternalRefInLoop会最慢,但它是第二快,而且只占整体冠军的一小部分!

问题:这些测试定义了小的函数,这妨碍了单独隔离函数定义对性能的影响。我不是100%肯定我的测试代码或测试用例是100%洁净的。最后,我的环境(SO的片段系统,在最新的Edge浏览器中)可能无法在所有环境中都适用,并且一般而言,优化可能会因环境而异。

问题:

  • 似乎解释器具有优化同一函数重复定义的巧妙方法,甚至可能 prefer 使用外部引用的函数我们应该始终喜欢在循环中定义函数,还是应该更喜欢使用外部引用(仅就性能而言),或者是否存在一些因素会间接阻止这些优化并导致这些策略减少 性能
  • 关于功能定义,其他有关性能的注意事项是什么? (我敢肯定,范围太广了,但是我想感谢任何人想提供的见解!)

注意:不建议过早优化,但并非所有优化都为时过早!

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)