动态内存分配导致影响Javascript性能的问题
内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存。不过直到最近优化HoLacantk时,我才深刻的体会到内存分配对性能的影响,其中有一个关于arguments的问题挺有意思,写在这里和大家分享一下。
我要做的事情是用webgl实现canvas的2dAPI(这个话题本身也是挺有意思的,有空我们再讨论),drawImage是一个重要的函数,游戏会频繁的调用它,所以它的性能至关重要。drawImage的参数个数是可变的,它有三种形式:
- drawImage(image,x,y)
- drawImage(image,x,y,width,height)
- drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)
第一个版本大概是这样实现的:
functionContext(){ } Context.prototype.drawImage3=function(image,x,y){ this.drawImage9(image,0,0,image.width,image.height,x,y,image.width,image.height); } Context.prototype.drawImage5=function(image,dx,dy,dw,dh){ this.drawImage9(image,0,0,image.width,image.height,dx,dy,dw,dh); } Context.prototype.drawImage9=function(image,sx,sy,sw,sh,dx,dy,dw,dh){ //DOIT } Context.prototype.drawImage=function(image,a,b,c,d,e,f,g,h){ varn=arguments.length; if(n===3){ this.drawImage3(image,a,b); }elseif(n===5){ this.drawImage5(image,a,b,c,d); }elseif(n===9){ this.drawImage9(image,a,b,c,d,e,f,g,h); } }
为了方便说明问题,我把测试程序独立出来:
varimage={width:100,height:200}; varctx=newContext(); functiontest(){ vara=Math.random()*100; varb=Math.random()*100; varc=Math.random()*100; vard=Math.random()*100; vare=Math.random()*100; varf=Math.random()*100; varg=Math.random()*100; varh=Math.random()*100; for(vari=0;i<1000;i++){ ctx.drawImage(image,a,b); ctx.drawImage(image,a,b,c,d); ctx.drawImage(image,a,b,c,d,e,f,g,h); } } window.onload=function(){ functionloop(){ test(); requestAnimationFrame(loop); } requestAnimationFrame(loop); }
用chrome的Profile查看CPU的使用情况时,我发现垃圾回收的时间比例很大,一般在4%以上。当时并没有怀疑到drawImage这个函数,理由很简单:
这个函数很简单,它只是一个简单的分发函数,而drawImage9的实现相对来说要复杂得多。
这里看不出有动态内存分配,也没有违背arguments的使用规则,只是使用了它的length属性。
加trace_opt和trace_deopt参数运行时,drawImage被优化了,而且没有被反优化出来。
Chrome的内存Profile只能看到没有被释放的对象,用它查看内存泄露比较容易。这里的问题并不是泄露,而是分配了然后又释放了,V8采用的分代垃圾回收器,这种短时存在的对象是由年轻代回收器管理器负责的,而年轻代回收器使用的半空间(semi-space)算法,这种大量短时间生存的对象,很快会耗尽其中一半空间,这时回收器需要把存活的对象拷贝到另外一半空间中,这就会耗费大量时间,而垃圾回收时会暂停JS代码执行,如果能避免动态内存分配,减少垃圾回收器的工作时间,就能提高程序的性能。
没法在Chrome里查看动态分配内存的地方(呵呵,后面证实是我的无知),只好去硬着头皮看V8JS引擎的代码,看看能不能找到频繁分配内存的地方,后来找到了V8统计内存分配的代码:
voidHeap::OnAllocationEvent(HeapObject*object,intsize_in_bytes){ HeapProfiler*profiler=isolate_->heap_profiler(); if(profiler->is_tracking_allocations()){ profiler->AllocationEvent(object->address(),size_in_bytes); } if(FLAG_verify_predictable){ ++allocations_count_; //Advancesynthetictimebymakingatimerequest. MonotonicallyIncreasingTimeInMs(); UpdateAllocationsHash(object); UpdateAllocationsHash(size_in_bytes); if(allocations_count_%FLAG_dump_allocations_digest_at_alloc==0){ PrintAlloctionsHash(); } } if(FLAG_trace_allocation_stack_interval>0){ if(!FLAG_verify_predictable)++allocations_count_; if(allocations_count_%FLAG_trace_allocation_stack_interval==0){ isolate()->PrintStack(stdout,Isolate::kPrintStackConcise); } } }
HeapProfiler已经有了内存分配的统计代码,Chrome里应该有对应的接口啊。再去看Chrome的Profile相关界面,最后发现需要在设置里勾选Recordheapallocationstacktraces,然后使用Recordheapallocations功能,查看结果时选择Allocations,可以看到每个函数分配内存的次数。有时一个问题折腾你好久,解决之前百思不得其解,觉得难得不得了,而解决之后忍不住要苦笑,原来只是一层窗户纸!
虽然还是不知道导致动态内存分配的原因(谁知道请告诉我),至少可以想法规避它:
Context.prototype.drawImage=function(){ varn=arguments.length; if(n===3){ this.drawImage3.apply(this,arguments); }elseif(n===5){ this.drawImage5.apply(this,arguments); }elseif(n===9){ this.drawImage9.apply(this,arguments); } }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接