动态内存分配导致影响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 相关文章推荐
JS操作select下拉框动态变动(创建/删除/获取)
Jun 02 Javascript
JavaScript获取图片的原始尺寸以宽度为例
May 04 Javascript
js单词形式的运算符
May 06 Javascript
js使用DOM设置单选按钮、复选框及下拉菜单的方法
Jan 20 Javascript
jquery插件splitScren实现页面分屏切换模板特效
Jun 16 Javascript
详解WordPress开发中get_current_screen()函数的使用
Jan 11 Javascript
Bootstrap carousel轮转图的使用实例详解
May 17 Javascript
浅谈AngularJs指令之scope属性详解
Oct 24 Javascript
JS 调用微信扫一扫功能
Dec 22 Javascript
微信小程序 动态绑定数据及动态事件处理
Mar 14 Javascript
javascript动态创建对象的属性详解
Nov 07 Javascript
JS开发前端团队展示控制器来为成员引流
Aug 14 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程序检查字符串中的中文字符个数的实例分享
2016/03/17 PHP
PHP实现将几张照片拼接到一起的合成图片功能【便于整体打印输出】
2017/11/14 PHP
PHP ajax+jQuery 实现批量删除功能实例代码小结
2018/12/06 PHP
php 中self,this的区别和操作方法实例分析
2019/11/04 PHP
javascript入门·图片对象(无刷新变换图片)\滚动图像
2007/10/01 Javascript
jquery 多行滚动代码(附详细解释)
2010/06/17 Javascript
给事件响应函数传参数的四种方式小结
2013/12/05 Javascript
Javascript中的回调函数和匿名函数的回调示例介绍
2014/05/12 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
2015/03/04 Javascript
多功能jQuery树插件zTree实现权限列表简单实例
2016/07/12 Javascript
Javascript类型系统之undefined和null浅析
2016/07/13 Javascript
基于JavaScript实现全选、不选和反选效果
2017/02/15 Javascript
async/await与promise(nodejs中的异步操作问题)
2017/03/03 NodeJs
javascript回调函数的概念理解与用法分析
2017/05/27 Javascript
js实现本地图片文件拖拽效果
2017/07/18 Javascript
JavaScript 中定义函数用 var foo = function () {} 和 function foo()区别介绍
2018/03/01 Javascript
Vue 表情包输入组件的实现代码
2019/01/21 Javascript
jquery实现抽奖功能
2020/10/22 jQuery
vue-amap根据地址回显地图并mark的操作
2020/11/03 Javascript
解决element-ui的下拉框有值却无法选中的情况
2020/11/07 Javascript
Python基础之函数用法实例详解
2014/09/10 Python
Python获取邮件地址的方法
2015/07/10 Python
python编写朴素贝叶斯用于文本分类
2017/12/21 Python
python远程连接服务器MySQL数据库
2018/07/02 Python
解决pyttsx3无法封装的问题
2018/12/24 Python
python实现转圈打印矩阵
2019/03/02 Python
python字典key不能是可以是啥类型
2020/08/04 Python
详解使用Python写一个向数据库填充数据的小工具(推荐)
2020/09/11 Python
canvas 绘图时位置偏离的问题解决
2020/09/16 HTML / CSS
全球领先美式家具品牌:Ashley爱室丽家居
2017/08/07 全球购物
Calzedonia美国官网:意大利风格袜子、打底裤和沙滩装
2018/07/19 全球购物
泰国健康和美容服务预订网站:GoWabi
2019/06/03 全球购物
Foot Locker澳洲官网:美国运动服和鞋类零售商
2019/10/11 全球购物
销售人员工作自我评价
2014/09/21 职场文书
家长高考寄语
2015/02/27 职场文书
酒店收银员岗位职责
2015/04/07 职场文书