动态内存分配导致影响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 相关文章推荐
javascript 获取页面的高度及滚动条的位置的代码
May 06 Javascript
8款非常棒的响应式jQuery 幻灯片插件推荐
Feb 02 Javascript
关于js遍历表格的实例
Jul 10 Javascript
jQuery插件 selectToSelect使用方法
Oct 02 Javascript
JavaScript子类用Object.getPrototypeOf去调用父类方法解析
Dec 05 Javascript
javascript 数字格式化输出的实现代码
Dec 10 Javascript
流量统计器如何鉴别C#:WebBrowser中伪造referer
Jan 07 Javascript
超赞的jQuery图片滑块动画特效代码汇总
Jan 25 Javascript
动态加载JavaScript文件的两种方法
Apr 22 Javascript
js输出数据精确到小数点后n位代码
Jul 02 Javascript
Angular-Ui-Router+ocLazyLoad动态加载脚本示例
Mar 02 Javascript
解决vue项目中出现Invalid Host header的问题
Nov 17 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 命令行参数详解及应用
2011/05/18 PHP
smarty 缓存控制前的页面静态化原理
2013/03/15 PHP
PHP 实现浏览记录并按日期分组
2017/05/11 PHP
TP3.2框架分页相关实现方法分析
2020/06/03 PHP
javascript字典探测用户名工具
2006/10/05 Javascript
jqGrid jQuery 表格插件测试代码
2011/08/23 Javascript
JavaScript中常用的运算符小结
2012/01/18 Javascript
nodejs教程之入门
2014/11/21 NodeJs
又一款MVVM组件 构建自己的Vue组件(2)
2017/03/13 Javascript
详解nodejs微信公众号开发——2.自动回复
2017/04/10 NodeJs
详解Node.js access_token的获取、存储及更新
2017/06/20 Javascript
通过JS深度判断两个对象字段相同
2019/06/14 Javascript
React学习之受控组件与数据共享实例分析
2020/01/06 Javascript
python中 ? : 三元表达式的使用介绍
2013/10/09 Python
Python减少循环层次和缩进的技巧分析
2016/03/15 Python
Python实现求笛卡尔乘积的方法
2017/09/16 Python
pycharm远程调试openstack的图文教程
2017/11/21 Python
Python matplotlib绘图可视化知识点整理(小结)
2018/03/16 Python
Python去除、替换字符串空格的处理方法
2018/04/01 Python
对python抓取需要登录网站数据的方法详解
2018/05/21 Python
python代码过长的换行方法
2018/07/19 Python
python基于SMTP协议发送邮件
2019/05/31 Python
利用Python绘制Jazz网络图的例子
2019/11/21 Python
mac在matplotlib中显示中文的操作方法
2020/03/06 Python
python实现输入三角形边长自动作图求面积案例
2020/04/12 Python
国际领先的在线时尚服装和配饰店:DressLily
2019/03/03 全球购物
英国家居用品和家居装饰品购物网站:Cox & Cox
2019/08/25 全球购物
毕业实习个人鉴定范文
2013/12/10 职场文书
应届毕业生个人求职信范文
2014/01/29 职场文书
学校元旦晚会方案
2014/02/19 职场文书
班班通校本培训方案
2014/03/12 职场文书
2015年幼儿园教育教学工作总结
2015/05/25 职场文书
2015年图书馆个人工作总结
2015/05/26 职场文书
如何用python插入独创性声明
2021/03/31 Python
MySQL令人大跌眼镜的隐式转换
2021/08/23 MySQL
Android Studio实现简易进制转换计算器
2022/05/20 Java/Android