动态内存分配导致影响Javascript性能的问题


Posted in Javascript onDecember 18, 2018

内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存。不过直到最近优化HoLa cantk时,我才深刻的体会到内存分配对性能的影响,其中有一个关于arguments的问题挺有意思,写在这里和大家分享一下。

我要做的事情是用webgl实现canvas的2d API(这个话题本身也是挺有意思的,有空我们再讨论),drawImage是一个重要的函数,游戏会频繁的调用它,所以它的性能至关重要。drawImage的参数个数是可变的,它有三种形式:

  • drawImage(image, x, y)
  • drawImage(image, x, y, width, height)
  • drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

第一个版本大概是这样实现的:

function Context() {
}
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) {
  //DO IT
}
Context.prototype.drawImage = function(image, a, b, c, d, e, f, g, h) {
  var n = arguments.length;
  if(n === 3) {
    this.drawImage3(image, a, b);
  }else if(n === 5) {
    this.drawImage5(image, a, b, c, d);
  }else if(n === 9) {
    this.drawImage9(image, a, b, c, d, e, f, g, h);
  }
}

为了方便说明问题,我把测试程序独立出来:

var image = {width:100, height:200};
var ctx = new Context();
function test() {
  var a = Math.random() * 100;
  var b = Math.random() * 100;
  var c = Math.random() * 100;
  var d = Math.random() * 100;
  var e = Math.random() * 100;
  var f = Math.random() * 100;
  var g = Math.random() * 100;
  var h = Math.random() * 100;
  for(var i = 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() {
  function loop() {
    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里查看动态分配内存的地方(呵呵,后面证实是我的无知),只好去硬着头皮看V8 JS引擎的代码,看看能不能找到频繁分配内存的地方,后来找到了V8统计内存分配的代码:

void Heap::OnAllocationEvent(HeapObject* object, int size_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_;
  // Advance synthetic time by making a time request.
  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相关界面,最后发现需要在设置里勾选Record heap allocation stack traces,然后使用Record heap allocations功能,查看结果时选择Allocations,可以看到每个函数分配内存的次数。有时一个问题折腾你好久,解决之前百思不得其解,觉得难得不得了,而解决之后忍不住要苦笑,原来只是一层窗户纸!

虽然还是不知道导致动态内存分配的原因(谁知道请告诉我),至少可以想法规避它:

Context.prototype.drawImage = function() {
  var n = arguments.length;
  if(n === 3) {
    this.drawImage3.apply(this, arguments);
  }else if(n === 5) {
    this.drawImage5.apply(this, arguments);
  }else if(n === 9) {
    this.drawImage9.apply(this, arguments);
  }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Javascript 相关文章推荐
jquery中输入验证中一个不错的效果
Aug 21 Javascript
javascript实现上传图片并预览的效果实现代码
Apr 11 Javascript
JS左右无缝滚动(一般方法+面向对象方法)
Aug 17 Javascript
JavaScript实现表格排序方法
Jun 14 Javascript
基于jQuery实现中英文切换导航条效果
Sep 18 Javascript
AngularJS Controller作用域
Jan 09 Javascript
基于vue-element组件实现音乐播放器功能
May 06 Javascript
vue.js绑定事件监听器示例【基于v-on事件绑定】
Jul 07 Javascript
详解vue引入子组件方法
Feb 12 Javascript
jQuery实现可编辑的表格
Dec 11 jQuery
JavaScript实现动态留言板
Mar 16 Javascript
详解webpack的clean-webpack-plugin插件报错
Oct 16 Javascript
关于node-bindings无法在Electron中使用的解决办法
Dec 18 #Javascript
Makefile/cmake/node-gyp中区分判断不同平台的方法
Dec 18 #Javascript
JS监听滚动和id自动定位滚动
Dec 18 #Javascript
JS实现的tab页切换效果完整示例
Dec 18 #Javascript
CryptoJS中AES实现前后端通用加解密技术
Dec 18 #Javascript
antd组件Upload实现自己上传的实现示例
Dec 18 #Javascript
微信小程序解除10个请求并发限制
Dec 18 #Javascript
You might like
适合PHP初学者阅读的4本经典书籍
2016/09/23 PHP
Thinkphp事务操作实例(推荐)
2017/04/01 PHP
php模式设计之观察者模式应用实例分析
2019/09/25 PHP
表单元素事件 (Form Element Events)
2009/07/17 Javascript
jquery.validate使用攻略 第五步 正则验证
2010/07/01 Javascript
重构Javascript代码示例(重构前后对比)
2013/01/23 Javascript
JS比较两个时间大小的简单示例代码
2013/12/20 Javascript
修复bash漏洞的shell脚本分享
2014/12/31 Javascript
jQuery实现带渐显效果的人物多级关系图代码
2015/10/16 Javascript
BootStrap+Angularjs+NgDialog实现模式对话框
2016/08/24 Javascript
js代码实现下拉菜单【推荐】
2016/12/15 Javascript
Angularjs中使用layDate日期控件示例
2017/01/11 Javascript
angular.JS实现网页禁用调试、复制和剪切
2017/03/31 Javascript
node koa2实现上传图片并且同步上传到七牛云存储
2017/07/31 Javascript
form表单数据封装成json格式并提交给服务器的实现方法
2017/12/14 Javascript
详解react-refetch的使用小例子
2019/02/15 Javascript
详解微信小程序之一键复制到剪切板
2019/04/24 Javascript
JS实现压缩上传图片base64长度功能
2019/12/03 Javascript
js实现详情页放大镜效果
2020/10/28 Javascript
[47:50]Secret vs VP 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python虚拟环境virualenv的安装与使用
2016/12/18 Python
Python登录注册验证功能实现
2018/06/18 Python
idea创建springMVC框架和配置小文件的教程图解
2018/09/18 Python
python实现PDF中表格转化为Excel的方法
2020/06/16 Python
python如何编写类似nmap的扫描工具
2020/11/06 Python
Mixbook加拿大:照片书,照片卡,剪贴簿,年历和日历
2017/02/21 全球购物
亚洲最大的运动鞋寄售店:KicksCrew
2020/11/26 全球购物
Servlet的生命周期
2013/08/25 面试题
质检的岗位职责
2013/11/17 职场文书
电子信息专业自荐书
2014/02/04 职场文书
企业党员公开承诺书
2014/03/26 职场文书
小区门卫岗位职责范本
2014/08/24 职场文书
纪念九一八事变演讲稿:青少年应树立远大理想
2014/09/14 职场文书
个人先进事迹材料
2014/12/29 职场文书
解决SpringCloud Feign传对象参数调用失败的问题
2021/06/23 Java/Android
《火纹风花雪月无双》预告“神秘雇佣兵” 紫发剑客
2022/04/13 其他游戏