【问题标题】:Memory handling vs. performance内存处理与性能
【发布时间】:2017-06-16 12:12:02
【问题描述】:

我正在构建一个 WebGL 游戏,我已经开始研究性能瓶颈。我可以看到当 GC 进行时 FPS 有很多小的下降。因此,我创建了一个小型内存池处理程序。开始使用后还是看到很多GC,我可能会怀疑自己有什么问题。

我的内存池代码如下所示:

function Memory(Class) {
    this.Class = Class;
    this.pool = [];

  Memory.prototype.size = function() {
    return this.pool.length;
  };

  Memory.prototype.allocate = function() {
    if (this.pool.length === 0) {
        var x = new this.Class();
        if(typeof(x) == "object") {
            x.size = 0;
            x.push = function(v) { this[this.size++] = v; };
            x.pop = function() { return this[--this.size]; };
        }
        return x;
    } else {
      return this.pool.pop();
    }
  };

  Memory.prototype.free = function(object) {
      if(typeof(object) == "object") {
          object.size = 0;
      }
      this.pool.push(object);
  };

  Memory.prototype.gc = function() {
    this.pool = [];
  };
}

然后我像这样使用这个类:

game.mInt = new Memory(Number);
game.mArray = new Memory(Array); // this will have a new push() and size property.
 // Allocate an number
 var x = game.mInt.allocate();

 <do something with it, for loop etc>

 // Free variable and push into mInt pool to be reused.
 game.mInt.free(x);

我对数组的内存处理基于使用 myArray.size 而不是 length,它会跟踪超维数组(已被重用)中的实际当前数组大小。

所以对于我的实际问题:

使用这种方法可以避免 GC 并在播放期间保留内存。我在函数内部使用“var”声明的变量是否仍然是 GC,即使它们从我的 Memory 函数作为 new Class() 返回?

例子:

var x = game.mInt.allocate();
for(x = 0; x < 100; x++) {
   ...
}
x = game.mInt.free(x);

由于幕后的一些内存复制,这是否仍会导致“var”的内存垃圾收集? (这会使我的内存处理程序无用)

对于我想要获得高 FPS 的游戏,我的方法是否良好/有意义?

【问题讨论】:

  • 基元按值复制。 => 我不知道分配值是否有用..
  • 我只会做 x=5;然后 x=undefined...
  • @jonasw 你的意思是例如:x = game.mInt.allocate();游戏.mint.free(x); x = 未定义;还是您的意思是简单地跳过我的内存处理? (x = undefined 会导致 GC 启动,对吗?)
  • 我会跳过它。新号码(5);创建一个数字对象,与原语相比,它的内存消耗相当大。

标签: javascript memory-management garbage-collection


【解决方案1】:

所以你让 JS 实例化一个新的 Object

var x = new this.Class();

然后将匿名方法添加到该对象,从而使其成为独一无二的对象

x.push = function...
x.pop = function...

所以现在你使用这个对象的每个地方都更难被 JS 引擎优化,因为它们现在有不同的接口/隐藏类(相等不一样)

此外,您使用这些对象的每个地方都必须实现额外的类型转换,以将Number Object 转换回原语,并且类型转换也不是免费的。就像,在循环的每次迭代中?甚至多次?

所有这些开销只是为了存储一个 64 位浮点数?

game.mInt = new Memory(Number);

而且由于您无法更改内部状态以及 Number 对象的值,因此这些值基本上是静态的,就像它们的原始对应物一样。

TL;DR:

  • 不要合并原生类型,尤其是原语。这些天来,如果 JS 不必处理意外情况,它就非常擅长优化代码。令人惊讶的是,具有不同接口的不同对象首先必须转换为原始值,然后才能使用。

  • 数组调整大小也不是免费的。尽管 JS 对此进行了优化并且通常预分配的内存比 Array 可能需要的多,但您仍可能达到该限制,因此强制引擎分配新内存,将所有值移动到该新内存并释放旧内存。
    我通常对池使用链表。

  • 不要试图汇集所有内容。想想哪些对象可以真正被重用,以及你正在努力将它们融入这种“可重用性”的叙述中。
    我想说:如果您只需将一个新属性添加到一个对象(在它被构造之后),那么您就需要delete 这个属性用于清理,这个对象应该被池化。

  • 隐藏类:在谈论 JS 中的优化时,您至少应该在非常基本的层面上了解这个主题
    总结:

    • 在构造对象后不要添加新属性。
    • 为了扩展第一点,没有deletes!
    • 添加属性的顺序很重要
    • 改变属性的值(甚至它的类型) 没关系!除非我们谈论包含函数(又名方法)的属性。当我们谈论附加到对象的函数时,优化器在这里可能有点挑剔,所以避免它。
  • 最后但同样重要的是:优化对象和“字典”对象之间的区别。首先在您的概念中,然后在您的代码中。
    尝试使用静态接口(这是 JS,而不是 Java)将所有内容都放入模式中没有任何好处。但是静态类型使优化器的工作更轻松。所以把两者组合起来。

【讨论】:

  • 非常感谢您提供如此详细的解释!这一切都说得通,我将重新调查我对池等的需求。
  • “在构造对象后不要添加新属性。” - 这可以通过在构造函数末尾调用 Object.seal(this) 来强制执行,假设您不这样做'不想继承那个类。
猜你喜欢
  • 2010-12-26
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
  • 1970-01-01
  • 2013-05-09
  • 2019-06-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多