修改!经过大量的后续研究表明我的问题没有一个简单的答案,我改变了自己的答案。见下文!
因此,在我的上一个问题的后续内容中,我正在尝试更好地处理最佳Javascript实践以优化性能。对于以下示例,我使用浏览器中的分析器在Chrome 28.0.1500.70中进行测试。
我有一些数学函数封装在一个对象中,每秒被调用几百k次,我试图削减一点执行时间。
我已经通过将父对象locals的本地副本作为被调用函数本身的本地副本进行了一些优化,并获得了不错的(~16%)性能提升。但是,当我从父对象调用另一个函数时,我的性能提升了很多(~100%)。
原始设置是calcNeighbors通过this.cirInd调用其他父对象函数cirInd。
制作一个cirInd的本地var副本并调用它来获得巨大的性能提升,不到之前calcAlighbors的执行时间的一半。
但是,在calcNeighbors中使用cirInd内联函数会导致返回与从父对象调用它相同的性能。
我真的为此感到困惑。我想这可能是Chrome的分析器中的一个怪癖(在第二种情况下,它根本没有出现)但是当我使用案例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 :(得分:1)
第1号涉及额外时间的原因应该是显而易见的。您可以访问整个对象范围,然后必须找到属性。
数字2和3都是指向函数的指针,因此没有寻求。
用于测试这些类型情况的非常好的资源是jsPerf,我强烈建议在那里重新创建场景并运行测试以查看确切的差异以及它们对您是否重要。
答案 1 :(得分: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
函数是对象,使新对象永远不会自由。
编辑:扩展#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 ]
理解在一个简单的例子中有更多的噪音,而闭包是all3中开销的一大部分,但它们之间的差异是重要的。
在这个演示中,你不会看到动态系统中观察到的巨大增益,但你确实看到y和out.y配置文件与this.y相比有多接近。所有其他条件相同。
主要的一点是,这不是额外的点分辨率本身会降低速度,正如一些人所暗示的那样,特别是V8中的“this”关键字很重要,否则out.y()会更接近于此.Y()...
firefox是一个不同的故事。追踪允许这个。无论如何预测,所以三个配置文件中的所有三个配置文件都在一个坏的骰子中滚动,与Chrome相同:[2548,2532,2545] ......
答案 2 :(得分:0)
好的,我一直在研究这个问题一段时间和TL; DR - 它很复杂。
事实证明,许多性能问题确实取决于平台,浏览器甚至是次要的浏览器版本号。而且也不是一点点。 jsPerf上有很多例子可以显示'for vs while;或者'类型化阵列与标准阵列'在不同浏览器版本的有利执行速度方面来回摆动。据推测,这是由于JIT优化权衡。
对一般性能问题的简短回答 - 只需测试jsPerf中的所有内容。我在这个帖子中得到的建议都没有在所有情况下都有用。 JIT让事情变得复杂。如果您拥有像我这样的背景并且习惯于具有某些经验法则编码模式的C程序,那么这一点尤为重要。不要假设 - 只是测试它。
注意:由于使用了默认的Chrome Profiler,我在原始问题中列出了许多奇怪的问题。 (例如:从Ctl + Shift + I菜单中获取的探查器)如果您正在进行大量非常快速的循环(例如在图形渲染中),请不要使用此配置文件。它的时间分辨率为1 ms,这太粗糙了,无法进行适当的性能调试。
事实上,我在案例2中比其他案件快得多的完整问题完全是由于分析器根本没有“看到”许多函数调用并且不正确地报告CPU百分比。在热图中,我可以清楚地看到内部循环功能正在发射的巨大延伸,但不是由探查器记录的。
解决方案:http://www.html5rocks.com/en/tutorials/games/abouttracing/# Chrome有一个不那么明显且功能强大的分析器,内置于:跟踪。它具有微秒级的分辨率,能够读取用于子功能分辨率的代码标签,并且通常更多的是kickass。一旦我开始使用这个分析器,结果就与我在jsPerf上看到的结果一致,并帮助我将渲染时间缩短了近一半。我是怎么做到的?再说一次,这并不简单。在某些情况下,调用子程序会有所帮助,而在其他情况下却没有。将整个渲染引擎从对象文字重构为模块模式似乎有点帮助。预处理for循环中的任何乘法运算似乎都有很大的影响。等等。
关于about:tracing profiler的快速说明:缩放和平移是在键盘上使用ASWD - 我花了一段时间才弄明白。此外,它还会分析所有选项卡,并在正在分析的页面外的选项卡中进行操作。因此,尽量减少已打开的无关选项卡的数量,因为它们会使分析器视图变得混乱。此外,如果测试Canvas应用程序,请确保将标签切换到应用程序,因为当标签未处于活动状态且不可见时,RequestAnimationFrame通常不会触发。