nergal
nergal

Reputation: 171

Memory handling vs. performance

I'm building a WebGL game and I've come so far that I've started to investigate performance bottlenecks. I can see there are a lot of small dips in FPS when there are GC going on. Hence, I created a small memory pool handler. I still see a lot of GC after I've started to use it and I might suspect that I've got something wrong.

My memory pool code looks like this:

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 = [];
  };
}

I then use this class like this:

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);

My memory handling for an array is based on using myArray.size instead of length, which keeps track of the actual current array size in an overdimensioned array (that has been reused).

So to my actual question:

Using this approach to avoid GC and keep memory during play-time. Will my variables I declare with "var" inside functions still be GC even though they are returned as new Class() from my Memory function?

Example:

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

Will this still cause memory garbage collection of the "var" due to some memcopy behind the scenes? (which would make my memory handler useless)

Is my approach good/meaningful in my case with a game that I'm trying to get high FPS in?

Upvotes: 2

Views: 104

Answers (1)

Thomas
Thomas

Reputation: 12637

So you let JS instantiate a new Object

var x = new this.Class();

then add anonymous methods to this object and therefore make it a one of a kind

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

so that now every place you're using this object is harder to optimize by the JS engine, because they have now distinct interfaces/hidden classes (equal ain't identical)

Additionally, every place you use these objects, will have to implement additional typecasts, to convert the Number Object back into a primitive, and typecasts ain't for free either. Like, in every iteration of a loop? maybe even multiple times?

And all this overhead just to store a 64bit float?

game.mInt = new Memory(Number);

And since you cannot change the internal State and therefore the value of a Number object, these values are basically static, like their primitive counterpart.

TL;DR:

  • Don't pool native types, especially not primitives. These days, JS is pretty good at optimizing the code if it doesn't have to deal with surprizes. Surprizes like distinct objects with distinct interfaces that first have to be cast to a primitive value, before they can be used.

  • Array resizing ain't for free either. Although JS optimizes this and usually pre-allocates more memory than the Array may need, you may still hit that limit, and therefore enforce the engine to allocate new memory, move all the values to that new memory and free the old one.
    I usually use Linked lists for pools.

  • Don't try to pool everything. Think about wich objects can really be reused, and wich you are bending to fit them into this narrative of "reusability".
    I'd say: If you have to do as little as adding a single new property to an object (after it has been constructed), and therefore you'd need to delete this property for clean up, this object should not be pooled.

  • Hidden Classes: When talking about optimizations in JS you should know this topic at least at a very basic level
    summary:

    • don't add new properties after an object has been constructed.
    • and to extend this first point, no deletes!
    • the order in wich you add properties matters
    • changing the value of a property (even its type) doesn't matter! Except when we talk about properties that contain functions (aka. methods). The optimizer may be a bit picky here, when we're talking about functions attached to objects, so avoid it.
  • And last but not least: Distinct between optimized and "dictionary" objects. First in your concepts, then in your code.
    There's no benefit in trying to fit everything into a pattern with static interfaces (this is JS, not Java). But static types make the life easier for the optimizer. So compose the two.

Upvotes: 2

Related Questions