欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

动态内存分配导致影响Javascript性能的问题

程序员文章站 2022-03-18 14:04:17
内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存。不过直到最近优化hola cantk时,我才深刻的体会到内...

内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存。不过直到最近优化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);
  }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。如果你想了解更多相关内容请查看下面相关链接