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?