动态内存分配导致Javascript性能的问题
内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存。不过直到最近优化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性能的问题的更多相关文章
- SQLite剖析之动态内存分配
SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.保存查询结果. 1.特性 SQLite内核和它的内存分配子系统提供以下特性 ...
- C和指针 第十一章 动态内存分配
声明数组时,必须指定数组长度,才可以编译,但是如果需要在运行时,指定数组的长度的话,那么就需要动态的分配内存. C函数库stdlib.h提供了两个函数,malloc和free,分别用于执行动态内存分配 ...
- C动态内存分配(C与指针实例)
主要初步介绍malloc.free.calloc.realloc的基本.日后会有更详细的内容. malloc.free分别用于动态内存分配和释放. malloc会从内存池里提取一块合适的内存(连续的) ...
- C++学习笔记(十一):void*指针、类型转换和动态内存分配
void*指针 void关键字表示“空类型”的概念.但是,这里的“空类型”不表示“任意类型”,而是表示不存在的意思,也就是说C/C++不允许你写语句void a,不存在类型为void的东西. void ...
- 转: Linux C 动态内存分配 malloc及相关内容 .
一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...
- 郑州尚学堂:链表的C语言如何实现动态内存分配
一.为什么用动态内存分配 但我们未学习链表的时候,如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组.比如说我们要存储一个班级学生的某科分数,总是定义一个float型(存在0.5分)数 ...
- 《C++ Primer Plus》读书笔记之十—类和动态内存分配
第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...
- Memcache 内存分配策略和性能(使用)状态检查【转】
前言: 一直在使用Memcache,但是对其内部的问题,如它内存是怎么样被使用的,使用一段时间后想看看一些状态怎么样?一直都不清楚,查了又忘记,现在整理出该篇文章,方便自己查阅.本文不涉及安装.操作. ...
- Linux C 动态内存分配--malloc,new,free及相关内容
一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...
随机推荐
- maven记录
1. 跳过测试 mvn -Dmaven.test.skip=true 2. 依赖树 mvn dependency:tree 3. 生成UTF-8的eclipse工程 构成目录中的.settings文 ...
- Servlet 生命周期、工作原理
按照单例的编码规则,Servlet本身只是一个Java,结构并不是单例结构. 只是Web容器在维护这些Servlet的时候只给创建一个实例存在JVM中,用户请求服务时,服务器只调用它已经实例化好的Se ...
- eNSP的使用
1- 进入华为路由器界面配置ipThe device is running!####################################Nov 1 2016 23:39:24-08:00 ...
- 传入一个label或者button,传入5s,6和6+的文字尺寸 快速定义文字大小
func isIphone6() -> Bool { { return true } else { return false } } func isIphone6Plus() -> Boo ...
- Nginx基础整理
目录结构如下: Nginx基础知识 Nginx HTTP服务器的特色及优点 Nginx的主要企业功能 Nginx作为web服务器的主要应用场景包括: Nginx的安装 安装环境 快速安装命令集合 各个 ...
- 【转】MySQL数据库MyISAM和InnoDB存储引擎的比较
MySQL有多种存储引擎,MyISAM和InnoDB是其中常用的两种.这里介绍关于这两种引擎的一些基本概念(非深入介绍). MyISAM是MySQL的默认存储引擎,基于传统的ISAM类型,支持全文搜索 ...
- MVC4中重复使用JQuery Mobile Dialog的做法实践.
第一步:建立mobile项目类型 第二步:添加针对对话框的的DialogController.cs: 建立这个Controller的目的是此Dlg可以反复使用,把它做成一个固定界面,其他的Contro ...
- [翻译] java NIO Buffer
原文地址:http://tutorials.jenkov.com/java-nio/buffers.html JAVA NIO 是在和channel交互的时候使用的.正如你所知道的,数据是从chann ...
- Kindle 推送教程:教你用电子邮箱推送电子书(Kindle伴侣)
Kindle 推送是什么意思?如何通过电子邮件附件推送?或许刚刚接触 Kindle 的朋友对这个概念不是很清楚,其实所谓 Kindle 推送是指亚马逊提供的一个"Kindle 个人文档服务& ...
- :radio :checkbox
匹配所有单选按钮 示例 描述: 查找所有单选按钮 HTML 代码: <form> <input type="text" /> <input typ ...