动态内存分配导致影响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 &amp; DHTML 实例编程(教程)(三)初级实例篇1—上传文件控件实例
Jun 02 Javascript
JavaScript 异步调用框架 (Part 3 - 代码实现)
Aug 04 Javascript
javascript制作loading动画效果 loading效果
Jan 14 Javascript
javascript中Number对象的toString()方法分析
Dec 20 Javascript
jQuery中:checked选择器用法实例
Jan 04 Javascript
jQuery插件jRumble实现网页元素抖动
Jun 05 Javascript
javascript实现密码验证
Nov 10 Javascript
JavaScript的设计模式经典之建造者模式
Feb 24 Javascript
文件上传的几个示例分享【推荐】
Dec 16 Javascript
纯js三维数组实现三级联动效果
Feb 07 Javascript
详解vue-cli中使用rem,vue自适应
May 06 Javascript
javascript实现文字跑马灯效果
Jun 18 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断点续传之如何分割合并文件
2014/03/22 PHP
PHP7.0安装笔记整理
2015/08/28 PHP
thinkphp命名空间用法实例详解
2015/12/30 PHP
PHP手机短信验证码实现流程详解
2018/05/17 PHP
php ActiveMQ的安装与使用方法图文教程
2020/02/23 PHP
JavaScript高级程序设计 读书笔记之十 本地对象Date日期
2012/02/27 Javascript
JavaScript判断textarea值是否为空并给出相应提示
2014/09/04 Javascript
javascript弹出拖动窗口
2015/08/11 Javascript
AngularJS ng-style中使用filter
2016/09/21 Javascript
angular中实现控制器之间传递参数的方式
2017/04/24 Javascript
vue-cli脚手架config目录下index.js配置文件的方法
2018/03/13 Javascript
Smartour 让网页导览变得更简单(推荐)
2019/07/19 Javascript
茶余饭后聊聊Vue3.0响应式数据那些事儿
2019/10/30 Javascript
[01:13:17]Secret vs NB 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python中optparse模块使用浅析
2015/01/01 Python
python实现简单的计时器功能函数
2015/03/14 Python
介绍Python的Django框架中的静态资源管理器django-pipeline
2015/04/25 Python
Python 爬虫学习笔记之多线程爬虫
2016/09/21 Python
Python中matplotlib中文乱码解决办法
2017/05/12 Python
解决pycharm无法调用pip安装的包问题
2018/05/18 Python
pycharm部署、配置anaconda环境的教程
2020/03/24 Python
jenkins+python自动化测试持续集成教程
2020/05/12 Python
python 实现非极大值抑制算法(Non-maximum suppression, NMS)
2020/10/15 Python
HTML5+css3:3D旋转木马效果相册
2017/01/03 HTML / CSS
Skyscanner香港:机票比价, 平机票和廉价航空机票预订
2020/02/07 全球购物
项目经理的岗位职责
2013/11/23 职场文书
职业生涯规划书前言
2014/04/15 职场文书
《第一朵杏花》教学反思
2014/04/16 职场文书
员工生日会策划方案
2014/06/14 职场文书
支教个人总结
2015/03/04 职场文书
2015年公司保安年终工作总结
2015/05/14 职场文书
2016新年问候语大全
2015/11/11 职场文书
八年级物理教学反思
2016/02/19 职场文书
合作合同协议书
2016/03/21 职场文书
Html5获取用户当前位置的几种方式
2022/01/18 HTML / CSS
Elasticsearch 聚合查询和排序
2022/04/19 Python