4

编辑!在大量后续研究表明我的问题没有简单的答案后,我将答案更改为我自己的答案。见下文!

因此,在我上一个问题的后续行动中,我试图更好地处理最佳 Javascript 实践以优化性能。对于以下示例,我正在使用浏览器内分析器在 Chrome 28.0.1500.70 中进行测试。

我已经将一些数学函数封装在一个对象中,这些函数每秒被调用几百 k 次,并试图减少一些执行时间。

我已经通过将父对象本地的本地副本作为被调用函数本身的本地副本进行了一些优化,并获得了不错的(~16%)性能提升。然而,当我从父对象调用另一个函数时,我得到了巨大的(~100%)性能提升。

最初的设置是 calcNeighbors 通过 this.cirInd 调用同伴父对象函数 cirInd。

制作 cirInd 的本地 var 副本并调用它会带来巨大的性能提升,不到 calcNeighbors 之前执行时间的一半。

但是,将 cirInd 设置为 calcNeighbors 中的内联函数会导致性能恢复到与从父对象调用它相同的速度。

我真的很困惑。我想这可能是 Chrome 分析器中的一个怪癖(cirInd 在第二种情况下根本没有出现),但是当我使用案例 2 时,应用程序的性能肯定会显着提高。

有人可以解释为什么案例 2 比案例 1 快得多,但更重要的是,为什么案例 3 似乎没有任何性能提升?

有问题的功能在这里:

从父对象调用:

  window.bgVars = {
     <snip>
     "cirInd": function(index, mod){
        //returns modulus, array-wrapping value to implement circular array
        if(index<0){index+=mod;}
        return index%mod;
     },
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        var cirInd = this.cirInd;
        var neighbors = grid[this.cirInd(rep-foo-1, mod)] + grid[this.cirInd(rep-foo, mod)] + grid[this.cirInd(rep-foo+1, mod)] + grid[this.cirInd(rep-1, mod)] + grid[this.cirInd(rep+1, mod)] + grid[this.cirInd(rep+foo-1, mod)] + grid[this.cirInd(rep+foo, mod)] + grid[this.cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }

通过局部变量调用:

  window.bgVars = {
     <snip>
     "cirInd": function(index, mod){
        //returns modulus, array-wrapping value to implement circular array
        if(index<0){index+=mod;}
        return index%mod;
     },
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        var cirInd = this.cirInd;
        var neighbors = grid[cirInd(rep-foo-1, mod)] + grid[cirInd(rep-foo, mod)] + grid[cirInd(rep-foo+1, mod)] + grid[cirInd(rep-1, mod)] + grid[cirInd(rep+1, mod)] + grid[cirInd(rep+foo-1, mod)] + grid[cirInd(rep+foo, mod)] + grid[cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }

内联调用:

  window.bgVars = {
     <snip>
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        function cirInd(index, mod){
          //returns modulus, array-wrapping value to implement circular array
          if(index<0){index+=mod;}
          return index%mod;
        }
        var neighbors = grid[cirInd(rep-foo-1, mod)] + grid[cirInd(rep-foo, mod)] + grid[cirInd(rep-foo+1, mod)] + grid[cirInd(rep-1, mod)] + grid[cirInd(rep+1, mod)] + grid[cirInd(rep+foo-1, mod)] + grid[cirInd(rep+foo, mod)] + grid[cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }
4

3 回答 3

2

也许在简化视图中看到#2 和#3 将有助于说明对象创建的副作用。

我相信这应该很明显:

alls1=[];
alls2=[];

function inner1(){}
function outer1(){
     if(alls1.indexOf(inner1)===-1){ alls1.push(inner1); }
}


function outer2(){
   function inner2(){}
   if(alls2.indexOf(inner2)===-1){ alls2.push(inner2); }
}

for(i=0;i<10;i++){
   outer1();
   outer2();
}

alert([ alls1.length, alls2.length  ]); // shows: 1, 10

函数是对象,创建新对象永远不会是免费的。

编辑:扩展#1 vs #2

再一次,一个简化的例子将有助于说明:

function y(a,b){return a+b;}
var out={y:y};
var ob={
   y:y, 
   x1: function(a){ return this.y(i,a);},
   x2: function(a){ return y(i,a);},
   x3: function(a){ return out.y(i,a);}
}

var mx=999999, times=[], d2,d3,d1=+new Date;
for(var i=0;i<mx;i++){ ob.x1(-i) }
times.push( (d2=+new Date)-d1 );

for(var i=0;i<mx;i++){ ob.x2(-i) }
times.push( (d3=+new Date)-d2 );

for(var i=0;i<mx;i++){ ob.x3(-i) }
times.push( (+new Date)-d3 );

alert(times); // my chrome's typical: [ 1000, 1149, 1151 ]

了解在一个简单的示例中存在更多噪音,并且闭包是所有开销的很大一部分,但它们之间的差异才是重要的。

在这个演示中,您不会看到在动态系统中观察到的巨大增益,但您确实看到 y 和 out.y 配置文件与 this.y 相比有多接近,所有其他条件都相同。

主要的一点是,它本身并不是额外的点分辨率本身会减慢速度,正如一些人所暗示的那样,重要的是 V8 中的“this”关键字,否则 out.y() 会更接近 this.y( )...

Firefox 是一个不同的故事。

跟踪允许 this.whatever 被预测,因此所有三个配置文件都在一个坏骰子中,在与 chrome 相同的组合上:[2548、2532、2545]...

于 2013-07-11T02:59:54.587 回答
1

第 1 项所涉及的时间相对较多的原因应该是显而易见的。您访问整个对象范围,然后必须找到一个属性。

数字 2 和 3 都是指向函数的指针,因此没有查找。

jsPerf是测试这些类型情况的一个非常好的资源,我强烈建议在那里重新创建场景并运行测试以查看确切的差异以及它们是否对您很重要。

于 2013-07-11T02:43:48.393 回答
0

好的,我已经研究这个问题一段时间了,TL;DR - 这很复杂。

事实证明,许多性能问题实际上取决于平台、浏览器,甚至是次要的浏览器修订号。也不是一点点。jsPerf 上有很多例子显示了诸如“for vs while”之类的东西;或者“类型数组与标准数组”在不同浏览器版本的有利执行速度方面来回摆动。这可能是由于 JIT 优化权衡。

对一般性能问题的简短回答 - 只需在 jsPerf 中测试所有内容。我在这个线程中得到的建议在所有情况下都没有帮助。JIT 使事情变得复杂。如果您有像我这样的背景并且习惯于具有某些经验法则编码模式的 C 程序,这一点尤其重要,这些编码模式往往会加快速度。不要假设任何事情 - 只是测试它。

注意:我在原始问题中列出的许多奇怪问题都是由于使用了默认的 Chrome 分析器。(例如:您从 Ctl+Shift+I 菜单获得的分析器)如果您正在执行大量非常快速的循环(例如在图形渲染中),请不要使用此分析器。它的时间分辨率为 1 毫秒,这对于进行适当的性能调试来说太粗糙了。

事实上,案例 2 比其他案例快得多的整个问题完全是由于分析器根本没有“看到”许多函数调用并且不正确地报告 CPU 百分比。在热图中,我可以清楚地看到内部循环函数正在触发但没有被分析器记录的巨大延伸。

解决方案:http://www.html5rocks.com/en/tutorials/games/abouttracing/# Chrome 在 about:tracing 中内置了一个不太明显但功能更强大的分析器。它具有微秒级的分辨率,能够读取代码标签以进行子功能分辨率,并且通常更加出色。我一开始使用这个分析器,结果就与我在 jsPerf 上看到的一致,并帮助我将渲染时间减少了近一半。我是怎么做到的?再一次,这并不简单。在某些情况下,调用子程序会有所帮助,而在其他情况下则没有。将整个渲染引擎从对象文字重构为模块模式似乎有点帮助。在 for 循环中预先计算任何乘法运算似乎确实有很大的影响。等等等等

关于 about:tracing profiler 的快速说明:缩放和平移是使用键盘上的 ASWD 进行的 - 我花了一段时间才弄清楚。此外,它会分析所有选项卡并在正在分析的页面之外的选项卡中运行。因此,尽量减少您打开的无关选项卡的数量,因为它们会使分析器视图变得混乱。此外,如果测试 Canvas 应用程序,请务必将选项卡切换到应用程序,因为 RequestAnimationFrame 通常不会在选项卡不活动且不可见时触发。

于 2013-07-23T04:36:39.683 回答