Javascript对象池是否值得避免垃圾回收?

时间:2014-04-03 18:50:25

标签: javascript garbage-collection object-pooling

我正在尝试为在浏览器中运行的Javascript构建游戏工具包。我已经遇到了可怕的100ms +暂停,过多的垃圾收集会导致。这往往会破坏用户体验。

正如我所读到的,对此的补救措施是首先避免创建垃圾,例如通过池化和重用对象。我整理了一个简单的应用来测试这个概念: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来计算每秒大约六十次的帧。每一帧,我都经历了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

在任何情况下,我都没有从池中看到更好的性能,并且在许多情况下性能显着恶化。对于性能差距为10比1的Chrome来说尤其如此。

我在网上看过其他文章,表明这种技术可以提升他们的表现。我的测试中有缺陷吗?

或者我可能错过了这种方法的观点?例如。是否最好先预测性能(高达90%!),以防止GC在随机时间内中断超过16ms帧?

1 个答案:

答案 0 :(得分:0)

不确定你是否仍然关心这一点,但我相信使用Vector2.apply(object);实例化另一个必须通过垃圾收集清理的矢量对象实例。所以本质上你不是通过使用该方法来清空池中的矢量对象来保存任何对象清理。

所以简单地手动归零: http://jsfiddle.net/gk6Gn/1/