2013-07-11 109 views
3

编辑!在很多后续研究表明我的问题没有简单的答案后,我将自己的答案改为自己的答案。见下文!Javascript嵌套函数调用优化

所以,在后续我的最后一个问题,我想获得最佳的Javascript做法更好地处理来优化性能。对于下面的示例,我使用浏览器中的分析器在Chrome 28.0.1500.70中进行测试。

我有封装在一个对象一些数学函数,它们获取调用几百k次第二,我试图刮了一下的执行时间。

我已经完成了通过使母公司的本地副本对象当地人当地人在被调用函数本身的一些优化,并得到一个体面的(〜16%)的性能提升。但是,当我从父对象调用另一个函数时,我得到了巨大(〜100%)的性能提升。

原来的设置是calcNeighbors呼吁通过this.cirInd同胞父对象功能cirInd。

制作cirInd的局部变量副本,并调用以前一样,而不是给了一个巨大的性能增益,只有不到一半的执行时间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> 
    } 
+0

“为什么3的情况下,似乎没有给出任何性能增益” ---为什么要移动一个功能相同的范围内做一些性能提升? – zerkms

+0

不是100%相关的但仍然有用:https://developers.google.com/v8/design#prop_access – zerkms

+2

#3产生一个新的函数cirInd()的每个调用,而#2回收的同一个每次通话。更少的激活创建=更快的运行时间和更少的垃圾清理。 – dandavis

回答

0

OK,我我一直在研究这个问题一段时间,TL; DR - 这很复杂。

原来,许多性能问题真的取决于平台,浏览器,即使是轻微的浏览器版本号。而不是一点一点。 jsPerf上有很多例子显示了'for vs while;或者“类型化数组与标准数组”在不同的浏览器版本中,在良好的执行速度方面来回摆动。推测这是由于JIT优化权衡。

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

注意:许多我原来的问题weer上市,由于使用默认浏览器探查怪异的问题。 (例如:您从Ctl + Shift + I菜单中获得的分析器)如果您正在执行许多非常快的循环(例如在图形渲染中),请勿使用此剖析器。它具有1毫秒的时间分辨率,这太粗糙而无法进行适当的性能调试。

其实整个问题,我曾与案件2是如此之快比别人完全是由于探查根本就没有“看到”许多函数调用和不当报告CPU百分比。在热图中,我可以清楚地看到内部循环函数正在触发但未被轮廓仪记录的巨大延伸。

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

对有关快速笔记:跟踪分析器:缩放和平移是在键盘上ASWD - 我花了一段时间才能弄清楚。此外,它还可以分析所有标签页,并在分析页面之外的标签页中进行操作。因此,尽量减少您打开的无关选项卡的数量,因为它们会使分析器视图杂乱无章。此外,如果测试Canvas应用程序,请确保将标签切换到应用程序,因为在标签不活动和可见时RequestAnimationFrame通常不会触发。

1

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

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

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

+0

'O(n)'? 我敢肯定,这是一个哈希表不是列表 http://stackoverflow.com/a/6602088/251311 – zerkms

+0

@zerkms - 那么,这是否来自于它是否是一个哈希访问属性的时间?我从我的答案中删除了时间复杂度假设。 –

+0

查看评论更新。即使对于一个哈希一样结构它仍然比'噢,更昂贵的(1)' – zerkms

1

可能在简化视图中看到#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 

功能是对象,并且使新的对象是永远免费的。

编辑:2

再次#1扩大VS#中,一个简单的例子将帮助说明:

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 ] 

明白,有一个简单的例子更多的噪音,和封闭是一个很大的所有3的开销都很大,但它们之间的差异是重要的。

在这个演示中,你将不会看到你的动态系统观察到巨大的收益,但你看如何接近y和out.y轮廓相比this.y,所有其他条件相同。

主要的一点是,它并不是多余的网点分辨率本身,正如一些人所暗示的那样,它具体是V8中的“this”关键字,否则out.y()会更接近于this.y()...

Firefox是一个不同的故事。

跟踪允许this.whatever进行预测,所以对方的坏骰子内所有三个配置文件,在相同组合的镀铬:2548,2532,2545] ...

+0

德普,你是对的,我不知道为什么我没有在#3中发现。尽管如此,我仍然有点困惑,为什么#1会带来如此巨大的表现。 – DanHeidel

+0

@DanHeidel:我会提供在#2中调用cirInd没有内部绑定,而它是#1中的整个对象。整个路径令牌通常由JIT快捷。但是,使用“这个”。意味着持有者对象必须在执行时重新评估。所有其他方面都是相同的,除了“this”之外,使用(几乎)任何单个标识符到点左侧应该描述同一个词法命名函数ref。 – dandavis

+0

有趣。因此,如果我正确地跟踪了你,对bgVars.cirInd而不是this.cirInd的引用将允许JIT避免运行时查找命中? – DanHeidel