1

我正在尝试为在浏览器中运行的 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 毫秒的帧?

4

1 回答 1

0

不确定您是否仍然关心这一点,但我相信 usingVector2.apply(object);正在实例化必须通过垃圾收集清理的向量对象的另一个实例。因此,从本质上讲,您不会通过使用该方法清除池中的矢量对象来保存任何对象清理。

所以只需手动取消:http: //jsfiddle.net/gk6Gn/1/

于 2017-03-20T18:02:08.273 回答