为什么重新分配功能指针会减慢功能调用

时间:2019-05-30 15:34:28

标签: javascript performance pointers benchmarking

我一直在使用函数指针p来调用不同的函数。我的问题是,如果在为p分配了一个函数之后将其指向另一个函数,则调用该函数的性能将大大降低。如果我一次又一次地在p和同一个功能之间切换undefined,则性能很好;当我仅将其指向一个功能,而在功能之间进行切换则会降低性能。

下面是我用来测试这种情况的代码,这里是fiddle。我递归循环500次,并在每个循环中调用p 1,000,000次。 p可以是undefined或指向func1func2

function func1() {} // two identical empty functions
function func2() {} // different in name only

var p = func1; // default to func1

var count  = 0; // current loop
var elapse = 0; // elapsed time for 1,000,000 calls on each loop
var start  = 0; // start time for 1,000,000 calls on each loop
var total  = 0; // total elapsed time for all loops

function loop() {

  start = performance.now(); // get start time

  for (let i = 0; i < 1000000; i ++) if (p !== undefined) p(); // do 1,000,000 calls or early out 1,000,000 times if undefined

    elapse = performance.now() - start;

  total += elapse; // used for getting average

  count ++;

  console.log(p + "\nelapsed " + elapse + "\naverage " + total / count);

  // Switch between the lines below to see the performance difference.
  p = (p === func1) ? p = undefined : p = func1; // best performance
  //p = (p === func1) ? p = func1 : p = func1; // good performance
  //p = (p === func1) ? p = func2 : p = func1; // bad performance

  // pattern: func1 -> undefined -> func2 -> undefined -> repeat
  /*if (p === undefined) p = (count % 4 === 0) ? p = func1 : p = func2;
  else p = undefined;*/ // also bad performance

  if (count < 500) loop(); // start the next loop

}

console.clear();

loop(); // start the loop

为什么给p分配了不同的功能后,其性能却显着下降?另外,为什么在将p设置为undefined然后再设置为其他功能时,将p设置为undefined并返回原始功能却不会改变性能?

2 个答案:

答案 0 :(得分:0)

您正在阻止引擎创建优化的热路径,因为它不能依赖函数指针的值。

请参见本文标题为“ JavaScript引擎中的解释器/编译器管道”的部分:https://mathiasbynens.be/notes/shapes-ics

图片显示了TurboFan基于执行中的数据分析来优化字节码,其后的文字说明:

  

为使其运行更快,可以将字节码与分析数据一起发送到优化编译器。优化编译器根据其具有的性能分析数据进行某些假设,然后生成高度优化的机器代码。

     

如果某个假设在某些时候被证明是不正确的,则优化的编译器会对图像进行反优化,然后返回解释器。

重新分配函数指针时,会将冲突的概要分析数据从解释器发送到编译器。当您分配undefined时,这不会发生,因为在这种情况下该代码路径不会执行:if (p !== undefined) p();

答案 1 :(得分:0)

  

即使我在指向func1的过程中调用一次p,然后将其分配给func2并在开始循环之前再次调用它,Chrome上仍然会损失约2.5 ms的性能。如果缓存已重置,我不知道丢失的原因。

您的V8心理模型不准确。在某些情况下,它可以将JS JIT编译为本地机器代码,但是它无法处理的任何事情都会“优化”整个函数(或者是块还是循环?)并进行解释。

我不是JS或V8的专家,但是我掌握了一些细节。 Google发现了这一点: https://ponyfoo.com/articles/an-introduction-to-speculative-optimization-in-v8

因此,并不是要使“缓存”失效一次,而是要删除基于其优化的不变条件。


将分支运行作为分支目标的缓存的分支预测仅在将JS运行时从JIT转换为本机代码而不是解释时才有意义。解释时,JS中的控件依赖项仅是在本机CPU上运行的解释器中的数据依赖项。

如果删除此不变式会优化函数或热循环,那么这就是您的问题,而不是分支预测。