动态内存分配导致影响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 相关文章推荐
漂亮的仿flash菜单,来自蓝色经典
Jun 26 Javascript
jQuery Tools tab(幻灯片)
Jul 14 Javascript
使用JavaScript实现旋转的彩圈特效
Jun 23 Javascript
JS随机调用指定函数的方法
Jul 01 Javascript
js实现的万能flv网页播放器代码
Apr 30 Javascript
bootstrap输入框组代码分享
Jun 07 Javascript
让div运动起来 js实现缓动效果
Jul 06 Javascript
JS获取当前地理位置的方法
Oct 25 Javascript
vue+webpack 打包文件 404 页面空白的解决方法
Feb 28 Javascript
Spring boot 和Vue开发中CORS跨域问题解决
Sep 05 Javascript
JS/HTML5游戏常用算法之碰撞检测 地图格子算法实例详解
Dec 12 Javascript
关于vue状态过渡transition不起作用的原因解决
Apr 09 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数组操作类实例
2015/07/11 PHP
给WordPress的编辑后台添加提示框的代码实例分享
2015/12/25 PHP
windows 2008r2+php5.6.28环境搭建详细过程
2019/06/18 PHP
Mootools 1.2教程 设置和获取样式表属性
2009/09/15 Javascript
jQuery 表单验证扩展代码(一)
2010/10/11 Javascript
JSON语法五大要素图文介绍
2012/12/04 Javascript
html+javascript实现可拖动可提交的弹出层对话框效果
2013/08/05 Javascript
jquery选择器需要注意的问题
2014/11/26 Javascript
浅谈javascript 迭代方法
2015/01/21 Javascript
ztree获取选中节点时不能进入可视区域出现BUG如何解决
2015/12/03 Javascript
jQuery添加删除DOM元素方法详解
2016/01/18 Javascript
JS回调函数基本定义与用法实例分析
2017/05/24 Javascript
详解vue-admin和后端(flask)分离结合的例子
2018/02/12 Javascript
vue.extend实现alert模态框弹窗组件
2018/04/28 Javascript
webuploader实现上传图片到服务器功能
2018/08/16 Javascript
微信小程序实现商品属性联动选择
2019/02/15 Javascript
vue2.0中set添加属性后视图不能更新的解决办法
2019/02/22 Javascript
element-ui如何防止重复提交的方法步骤
2019/12/09 Javascript
[02:54]DOTA2英雄基础教程 暗影牧师戴泽
2013/12/05 DOTA
详谈Python 窗体(tkinter)表格数据(Treeview)
2018/10/11 Python
Python数据集切分实例
2018/12/08 Python
python队列原理及实现方法示例
2019/11/27 Python
python GUI库图形界面开发之PyQt5布局控件QGridLayout详细使用方法与实例
2020/03/06 Python
python GUI库图形界面开发之PyQt5信号与槽多窗口数据传递详细使用方法与实例
2020/03/08 Python
python实现简单坦克大战
2020/03/27 Python
Python3爬虫中Splash的知识总结
2020/07/10 Python
意大利奢华内衣制造商:Cosabella
2017/08/29 全球购物
台湾网购生鲜第一品牌:i3Fresh爱上新鲜
2017/10/26 全球购物
Smallable意大利家庭概念店:设计师童装及家居装饰
2018/01/08 全球购物
CheapTickets泰国:廉价航班,查看促销价格并预订机票
2019/12/28 全球购物
上海中网科技笔试题
2012/02/19 面试题
写演讲稿所需要注意的4个条件
2014/01/09 职场文书
法律顾问服务方案
2014/05/15 职场文书
机械专业应届毕业生自荐书
2014/06/12 职场文书
党支部党的群众路线对照检查材料
2014/09/24 职场文书
Python爬虫入门案例之爬取二手房源数据
2021/10/16 Python