我正在尝试为在浏览器中运行的 Javascript 构建游戏工具包。我已经遇到了过多的垃圾收集可能导致的可怕的 100 毫秒以上的暂停。这往往会破坏用户体验。
正如我所读到的,对此的补救措施是首先避免创建垃圾,例如通过对象的池化和重用。我整理了一个简单的应用程序来测试这个概念:http: //jsfiddle.net/gk6Gn/
矢量类包含在源代码中,并且定义非常简单:
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;
};
我使用 requestAnimationFrame 每秒计算大约 60 次帧。每一帧,我都会进行 N 次迭代。在每次迭代中,我创建两个向量并将其相加,得到第三个向量。我慢慢增加迭代次数,直到性能下降到每秒 59 帧以下,并考虑我每帧的最大迭代次数:
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();
为了比较苹果和苹果,我设置了一个“经典”方法,我只是新建向量对象并将它们留给垃圾收集器,以及一个“池化”方法,我使用非收缩数组来存储向量重用的对象。在此示例中,池永远不会大于三个向量:
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);
}
}
对于我的汇总测试,这里是我的获取和释放函数:
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;
}
这一切都可以在所需的浏览器中运行,但我看到的性能结果完全令人印象深刻:
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
在任何情况下,我都看不到池化的更好性能,而且在许多情况下,性能要差得多。对于 Chrome 来说尤其如此,其性能差距为 10 比 1。
我在网上看到其他文章显示这种技术提高了他们的性能。我的测试有问题吗?
还是我可能错过了这种方法的重点?例如,最好预先对性能进行打击(高达 90%!),以防止 GC 在随机时间中断超过 16 毫秒的帧?