优化JavaScript功能性能
#javascript #测试 #性能 #deno

我最近解决了一个年龄段我的待办事项清单底部附近的编码问题。我什至设法将递归功能放入解决方案中。是的,计算机科学!我走开了,感觉自己做得好……但是感觉不对。我怀疑我对使用凉爽技术的兴奋导致了效率较低的算法。我需要进行一些实验,看看我的怀疑是否正确。

当我使用deno时,我决定使用内置的benchmarker tool,并开始编码我的测试套件。

参考函数

我写的递归函数是以the Lodash get method的方式解决并返回嵌套对象树中的位置。我想减少对外部功能的依赖性,因此决定不仅进口,而且事实证明这一点很容易:

export function arrayWise(ref, context) {
  const [key, ...rest] = ref.split(".");
  if (rest.length) {
    return arrayWise(rest.join("."), context[key]);
  }
  return context[key];
}

到目前为止,一切都很好。抓住引用,分为.字符上的数组,检查是否仍有级别要下降,如果是的,则将其下降到它们中,传递缩短的键并重置上下文以降低较低级别。如果没有,请从上下文返回查找值。此回报将通过呼叫堆栈冒出来,并作为原始呼叫的结果显示。

第一个替代方案

重复打开并加入ref变量使我感到困扰。这是一个操作的效率?

有了一些轻巧的修补,我想出了这种选择,这在分裂绳子方面有些外科手术:

export function stringWise(ref, context) {
  const cutMark = ref.indexOf(".");
  if (cutMark === -1) return context[ref];
  const key = ref.slice(0, cutMark);
  const rest = ref.slice(cutMark + 1);
  return stringWise(rest, context[key];
}

这次,我们在字符串中找到了第一个.的位置。如果没有,只需返回对象引用的数据即可。如果不是,则使用slice方法确定keyrest。然后返回调用下一层的结果。

写基准

让我们看看这是如何在性能方面堆叠的。

编写基准测试很轻松!我省略了导入并为简洁设置测试context对象。请注意,arrayWise函数被设置为基准情况。

Deno.bench({
  name: "arrayWise",
  baseline: true,
  fn: () => { arrayWise("look.me.up", context) 
});

Deno.bench({
  name: "stringWise",
  fn: () => { stringWise("look.me.up", context) }
});

这保存在名为lookup.bench.ts的文件中。当您运行deno bench时,命令提示符搅拌片刻,然后产生这样的输出:

Screenshot of the output of deno bench, showing the results table, which demonstrates that arrayWise is 3.34 times slower than stringWise. The table includes many measurements that describe the statistical distribution of the results, including average, minimum, maximum and percentile values

我的直觉是正确的。我的算法速度提高了三倍。那绝对是“第一,最糟糕的”尝试。

第二个选择

在这一点上,我开始对递归感到不安。称全新的功能感觉就像是一件相当沉重的事情。将其写成循环会更快吗?回到代码编辑器,我想到了:

export function nonRecursive(ref, context) {
  let result = context;
  for (const key of ref.split(".")) {
    result = result[key];
  }
  return result;
}

这次,我在此功能中的参考中循环浏览键,每次都重新分配结果。当我处于循环结束时,我已经得到了答案,所以我退还了。

我将新的基准测试添加到文件中,然后再次运行deno bench。这是这次的结果:

Screenshot of the output of deno bench, showing the results table, which demonstrates that arrayWise is 3.13 times slower than stringWise, but 5.28 times slower than nonRecursive. The table includes many measurements that describe the statistical distribution of the results, including average, minimum, maximum and percentile values

另一个加速,几乎是第一个优化的速度的两倍。事实证明,递归,虽然很酷的技术有时是过度杀伤,并且通常在线性处理(例如此示例)的情况下最好避免。

最后的选择

我以为我会看到我将典型(非常简洁的)JavaScript写作样式放在板凳上时发生了什么。

export const extremeGilesMode = (ref, context) =>
  ref.split(".")
     .reduce(
       (result, key) => result[key],
       context
     );

单线!我将是第一个承认的人,这比上一个示例要少得多。它将ref字符串分成组成部分,然后在结果数组上调用koude12 array method。这是在数组上迭代的,每次都会从上一个result返回key属性。 reduce的第二个参数设置了result的初始值。在这种情况下,我们将其设置为context。这是与nonRecursive函数几乎相同的技术。

这是简短的,但是它的速度更快吗?

Screenshot of the output of deno bench, showing the results table, which demonstrates that arrayWise is 3.33 times slower than stringWise, but 5.5 times slower than nonRecursive and 5.38 times slower than extremeGilesMode. The table includes many measurements that describe the statistical distribution of the results, including average, minimum, maximum and percentile values

并不是很快,看起来可能会稍慢一些。 nonRecursive实现是最快的。

我学到了什么?

这是一个内容丰富的实验。在这种情况下,鉴于在纳秒秒中测量了迭代的节省,因此离开初始实施可能会很好。请注意,如果被称为多次,这可能是一个显着的性能放缓。

还值得注意的是,每次运行都会在迭代中给出不同的时间,因此请谨慎引用确定的数字。分析性能时,值得查看百分位数(P75,P99和P995)以检查可变性。

总的来说,我对在DeNo设置基准的容易程度给我留下了深刻的印象。对于那些习惯于基准测试的人有一些局限性,但是作为起动器,它非常强大。

我已经捆绑了examples in a Github repo。通过评论让我知道您对代码的绩效改进。