动态内存分配导致影响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 相关文章推荐
showModelessDialog()使用详解
Sep 07 Javascript
判断客户端浏览器是否安装了Flash插件的多种方法
Aug 11 Javascript
修改jquery里的dialog对话框插件为框架页(iframe) 的方法
Sep 14 Javascript
jQuery ajax serialize()方法的使用以及常见问题解决
Jan 27 Javascript
js子页面获取父页面数据示例
May 15 Javascript
Javascript中this的用法详解
Sep 22 Javascript
HTML+CSS+JS实现完美兼容各大浏览器的TABLE固定列
Apr 26 Javascript
javascript滚轮事件基础实例讲解(37)
Feb 14 Javascript
JS实现多级菜单中当前菜单不随页面跳转样式而发生变化
May 30 Javascript
JavaScript 用fetch 实现异步下载文件功能
Jul 21 Javascript
解析vue路由异步组件和懒加载案例
Jun 08 Javascript
mpvue中使用flyjs全局拦截的实现代码
Sep 13 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简单系统查询模块代码打包下载
2008/06/07 PHP
基于simple_html_dom的使用小结
2013/07/01 PHP
php使用Jpgraph绘制简单X-Y坐标图的方法
2015/06/10 PHP
php修改数组键名的方法示例
2017/04/15 PHP
THINKPHP5分页数据对象处理过程解析
2020/10/28 PHP
JavaScript 对象的属性和方法4种不同的类型
2010/03/19 Javascript
跟着Jquery API学Jquery之一 选择器
2010/04/07 Javascript
jQuery实现图片信息的浮动显示实例代码
2013/08/28 Javascript
jquery属性选择器not has怎么写 行悬停高亮显示
2013/11/13 Javascript
jQuery之选项卡的简单实现
2014/02/28 Javascript
10个很棒的jQuery代码片段
2015/09/24 Javascript
轻松搞定jQuery.noConflict()
2016/02/15 Javascript
jQuery插件echarts实现的去掉X轴、Y轴和网格线效果示例【附demo源码下载】
2017/03/04 Javascript
原生JavaScript实现Tooltip浮动提示框特效
2017/03/07 Javascript
React如何利用相对于根目录进行引用组件详解
2017/10/09 Javascript
基于zepto.js实现登录界面
2017/10/09 Javascript
React中嵌套组件与被嵌套组件的通信过程
2018/07/11 Javascript
详解JS中统计函数执行次数与执行时间
2018/09/04 Javascript
Js中使用正则表达式验证输入是否有特殊字符
2018/09/07 Javascript
Vue实现动态添加或者删除对象和对象数组的操作方法
2018/09/21 Javascript
vue+elementUi 实现密码显示/隐藏+小图标变化功能
2020/01/18 Javascript
JavaScript 中的无穷数(Infinity)详解
2020/02/13 Javascript
JavaScript进阶(四)原型与原型链用法实例分析
2020/05/09 Javascript
在vue中使用Echarts画曲线图的示例
2020/10/03 Javascript
微信小程序淘宝首页双排图片布局排版代码(推荐)
2020/10/29 Javascript
JavaScript实现图片合成下载的示例
2020/11/19 Javascript
[42:00]完美世界DOTA2联赛PWL S3 Phoenix vs INK ICE 第一场 12.13
2020/12/17 DOTA
pygame游戏之旅 添加碰撞效果的方法
2018/11/20 Python
django数据库自动重连的方法实例
2019/07/21 Python
python创建ArcGIS shape文件的实现
2019/12/06 Python
Matplotlib使用Cursor实现UI定位的示例代码
2020/03/12 Python
解决Django no such table: django_session的问题
2020/04/07 Python
CSS3中新增的对文本和字体的设置
2020/02/03 HTML / CSS
数据库测试通常都包括哪些方面
2015/11/30 面试题
酒店管理专业毕业生自我鉴定
2014/09/29 职场文书
千万级用户系统SQL调优实战分享
2022/03/03 MySQL