1

I am trying to build out a game kit for Javascript running in a browser. I have already run into the dreaded 100ms+ pauses that excessive garbage collection can cause. This tends to wreck the user experience.

The remedy to this, as I have read it, is to avoid creating garbage in the first place, such as through pooling and reuse of objects. I put together a simple app to test out the concept: http://jsfiddle.net/gk6Gn/

The vector class is included in the source, and is very simply defined:

function Vector2()
{
    this.x = 0;

    this.y = 0;
}

Vector2.prototype.addUnpooled = function (other)
{
    var v = new Vector2();

    v.x = this.x + other.x;

    v.y = this.y + other.y;

    return v;
};

Vector2.prototype.addPooled = function (other)
{
    var v = acquireVector2();

    v.x = this.x + other.x;

    v.y = this.y + other.y;

    return v;
};

I use requestAnimationFrame to compute a frame about sixty times per second. Every frame, I run through N iterations. In each iteration, I create and add two vectors together, resulting in a third. I slowly ramp up the number of iterations until the performance degrades below 59 frames per second, and consider that my maximum iterations per frame:

function drawFrame(time)
{
    window.requestAnimationFrame(drawFrame);

    //testClassic();

    testPooled();

    framesSinceLastReport++;

    var timeSinceLastReport = time - lastReportTime;

    if (timeSinceLastReport >= 1000)
    {
        var framesPerSecond = Math.floor(framesSinceLastReport / (timeSinceLastReport / 10000)) / 10;

        output.innerHTML = framesPerSecond + ' fps @ ' + iterationsPerFrame + ' iter/frame';

        framesSinceLastReport = 0;

        lastReportTime = time;

        if (framesPerSecond >= 59) iterationsPerFrame = Math.floor(iterationsPerFrame * 1.2);
    }
}

drawFrame();

To compare apples to apples, I set up both a 'classic' approach where I just new the vector objects and leave them for the garbage collector, as well as a 'pooled' approach, where I use a non-shrinking array to store vector objects for reuse. In this example, the pool never gets larger than three vectors:

function testClassic()
{
    for (var i = 0; i < iterationsPerFrame; i++)
    {
        var a = new Vector2();

        a.x = 2;

        a.y = 3;

        var b = new Vector2();

        b.x = 1;

        b.y = 4;

        var r = a.addUnpooled(b);

        if (r.x != 2 + 1 || r.y != 3 + 4) throw 'Vector addition failed.';
    }
}

function testPooled()
{
    for (var i = 0; i < iterationsPerFrame; i++)
    {
        var a = acquireVector2();

        a.x = 2;

        a.y = 3;

        var b = acquireVector2();

        b.x = 1;

        b.y = 4;

        var r = a.addPooled(b);

        if (r.x != 2 + 1 || r.y != 3 + 4) throw 'Vector addition failed.';

        releaseVector2(a);

        releaseVector2(b);

        releaseVector2(r);
    }
}

For my pooled test, here are my acquire and release functions:

var vector2Pool = [];

vector2Pool.topIndex = -1;

function acquireVector2()
{
    if (vector2Pool.topIndex >= 0)
    {
        var object = vector2Pool[vector2Pool.topIndex];

        vector2Pool[vector2Pool.topIndex] = null;

        vector2Pool.topIndex--;

        Vector2.apply(object);

        return object;
    }
    else
    {
        return new Vector2();
    }
}

function releaseVector2(vector2)
{
    vector2Pool.topIndex++;

    vector2Pool[vector2Pool.topIndex] = vector2;
}

It all works in the desired browsers, but the performance results I am seeing are utterly underwhelming:

PC

  Chrome 33.0.1750.154 m

    unpooled  1077153 iter / frame

    pooled    100677 iter / frame

  Firefox 27.0.1

    unpooled  100677 iter / frame

    pooled    33718 iter / frame

  Internet Explorer 9.0.8112.16421

    unpooled  83898 iter / frame

    pooled    83898 iter / frame

iPhone 5, iOS 7.1

  Safari Mobile

    unpooled  208761 iter / frame

    pooled    144974 iter / frame

  Chrome

    unpooled  11294 iter / frame

    pooled    3784 iter / frame

iPad with Retina, iOS 7.1

  Safari Mobile

    unpooled  208761 iter / frame

    pooled    144974 iter / frame

In no case do I see better performance from pooling, and in many cases the performance is drastically worse. This is especially true for Chrome, where the performance gap is 10 to 1.

I have seen other articles online that show boosts in their performance from this kind of technique. Is there a flaw in my test?

Or am I perhaps missing the point of this approach? E.g. is it better to take a performance hit (up to 90%!) up front, to prevent the GC from interrupting at random times for longer than a 16ms frame?

Jim Noble
  • 492
  • 6
  • 12

1 Answers1

0

Not sure if you still care about this at all, but I believe using Vector2.apply(object); is instantiating another instance of the vector object that has to be cleaned up by garbage collection. So in essence you are not saving any object cleanup by using that method to null out the vector objects in your pool.

So simply null out manually: http://jsfiddle.net/gk6Gn/1/

Matthew Graves
  • 3,226
  • 1
  • 17
  • 20