【问题标题】:Understanding JavaScript heap growth and GC pattern了解 JavaScript 堆增长和 GC 模式
【发布时间】:2016-11-18 02:30:02
【问题描述】:

试图编写完美的 javascript 是徒劳的,我正在解决 Javascript 堆的问题。我已经把它降到了我能做到的最低水平,但我已经没有选择了,也不明白发生了什么(我猜是 rAF 开销,但猜测不算数)。

堆的锯齿图案(浅蓝色):

上面的时间线来自一个简单的全页画布粒子渲染。练习的目的是减少堆锯齿的幅度,并希望增加清理之间的时间间隔。

仔细观察,堆每 60 分之一秒增长约 15k,每约 1 秒从 3.3MB 下降到 2.4MB

我不明白的是时间和增长量15K。

在执行空闲之前堆增长了 15kb,并且在以下函数返回空闲后约 0.015ms(下面是我的顶级函数)。

var renderList = [];    
var stop = false;
var i;

function update(timer) { // Main update loop
    if(!stop){
        requestAnimationFrame(update);
    }
    for(i = 0; i < renderList.length; i ++){
        renderList[i](timer,ctx, w, h);            
    }
}

我对代码所做的任何事情都不会减少或更改堆增长的位置。分配配置文件显示没有分配任何内存。 GC 在 CPU 配置文件上为 0.08%(我不知道它在做什么?它是否也管理堆?)

有人可以向我解释一下这个内存是用来做什么的吗?以及如何减少它或使线条变平?

我知道我可能无能为力,但目前我不知道堆上的东西是什么?很高兴知道。

sn-p 只是从update 调用的代码(上面的代码sn-p)我认为它不相关,但以防万一。它是在堆增长之前执行并返回的代码。

        var p,d,s;
        var renderCount = 0;
        var fxId = 0;
        var lastTime;
        var frameTime = 0;
        var minParticles = 10;
        var particleCount = minParticles;
        var frameSum = 0;
        var frameAve = 0;
        var frameWorkTime = 0;
        var lastFrameWorkTime = 0;
        var particleRenderTimeMax = 0;
        var m = 0;
        var mC = 0;
        var mR = 0;
        var then,tx,ty;
        var renderTime,then1; 

        //=====================================================================================
        // the following function is out of context and just placed here as reference
        /*
        draw : function (image, index, x, y, scale, rotation, alpha) {
            spr = image.sprites[index];
            ctx.setTransform(scale, 0, 0, scale, x, y);
            ctx.rotate(rotation);
            ctx.globalAlpha = alpha;
            sh = spr.h;
            sw = spr.w;
            if(spr.vx !== undefined){  // virtual sprite dimensions
                _x = -spr.vw / 2 + spr.vx;
                _y = -spr.vh / 2 + spr.vy;
                ctx.drawImage(image, spr.x, spr.y, sw, sh, _x, _y, sw, sh);
                return;
            }
            ctx.drawImage(image, spr.x, spr.y, sw, sh, -sw / 2, -sh / 2, sw, sh);
        },
        */        
        //=====================================================================================        
        
        // Add particle
        function addP(x,y,spr){
            p = particles.fNextFree();
            if(particles.fLength >= particleCount || p === undefined){ // no room for more
                return;
            }
            p.x = x;
            p.y = y;
            p.spr = spr;
            p.life = 100;
            p.s = Math.random() +0.1
            d = Math.random() * Math.PI * 2;
            s = Math.random() * Math.PI * 2;
            p.dx = Math.cos(d) * s;
            p.dy = Math.sin(d) * s;
            p.dr = Math.random()-0.5;
            p.maxLife = p.life = 100-spr*10;
        }
        // move and draw particle
        function updateDrawP(p,i){
            if(i >= particleCount){
                p.life = undefined;
                return;
            }
            s =  p.life/p.maxLife;
            p.x += p.dx * s;
            p.y += p.dy * s;
            p.r += p.dr;
            p.life -= 1;
            
            if(p.life === 0){
                p.life = undefined;
                return;
            }
            renderCount += 1;
            sDraw(spriteSheet, p.spr, p.x, p.y, p.s, p.r, s); // Calls draw (function example above)
        }
      
        
        function renderAll(time) { // this is called from a requestAnimationFrame controlled function
            var then = performance.now(); // get frame start time
            var tx, ty;
            if (lastTime !== undefined) {
                frameTime = time - lastTime;
                frameSum *= 0.5;
                frameSum += frameTime;
                frameAve = frameSum * 0.5; // a running mean render time
            }
            lastTime = time;
            ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
            ctx.globalAlpha = 1; // reset alpha
            ctx.clearRect(0, 0, w, h);
            if (spriteSheet.sprites) { 
                mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                if (mouse.buttonRaw & 1) {
                    fxId += 1;
                    fxId %= EZSprites.FX.namedFX.length;
                    mouse.buttonRaw = 0;
                }
                if (mouse.buttonRaw & 4) {
                    world.posX += mouse.x - mouse.lastX;
                    world.posY += mouse.y - mouse.lastY;
                    EZSprites.world.setPosition(world.posX, world.posY);
                    mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                }
                if (mouse.w !== 0) {
                    if (mouse.w > 0) {
                        EZSprites.world.zoom2Screen(mouse.x, mouse.y, ZOOM_AMOUNT, true);
                        mouse.w -= ZOOM_WHEEL_STEPS;
                    } else {
                        EZSprites.world.zoom2Screen(mouse.x, mouse.y, ZOOM_AMOUNT, false);
                        mouse.w += ZOOM_WHEEL_STEPS
                    }
                    mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                    EZSprites.world.getWorld(currentWorld);
                    world.posX = currentWorld.x;
                    world.posY = currentWorld.y;
                }

                // sets the current composite operation (only using source-over)
                EZSprites.FX[EZSprites.FX.namedFX[fxId]]();

                // render and time particles
                renderCount = 0;
                var then1 = performance.now();
                
                particles.fEach(updateDrawP); // render all particles
                
                var renderTime = performance.now() - then1;

                EZSprites.context.setDefaults();

                // gets the total time spent inside this function
                frameWorkTime += performance.now() - then;
                lastFrameWorkTime = frameWorkTime;
                if (renderCount > 0) {
                    particleRenderTimeMax = Math.max(particleRenderTimeMax, renderTime / renderCount);
                    particleRenderTimeMax *= 10;
                    particleRenderTimeMax += renderTime / renderCount
                    particleRenderTimeMax /= 11;
                    // Smooth out per particle render time max
                    m = particleRenderTimeMax;
                    mC += (m - mR) * 0.1;
                    mC *= 0.1;
                    mR += mC;
                    // Particle count is being balanced to keep ensure there is plenty of idle time before
                    // the next frame. Mean time spent in this function is about 8 to 9ms
                    particleCount = Math.floor(((1000 / 120) - (frameWorkTime - renderTime)) / (mR));
                }
                // This is where frameWorkTime begins its timing of the function
                then = performance.now();
                frameWorkTime = 0;

                if (particleCount <= maxParticles) {
                    particles.fMaxLength = particleCount;
                }
                // Add particles. 
                addP(mouse.x, mouse.y, 1);
                addP(mouse.x, mouse.y, 2);
                addP(mouse.x, mouse.y, 3);
                addP(mouse.x, mouse.y, 4);
                addP(mouse.x, mouse.y, 5);
                addP(mouse.x, mouse.y, 1);
                addP(mouse.x, mouse.y, 2);
                addP(mouse.x, mouse.y, 3);
                addP(mouse.x, mouse.y, 4);
                addP(mouse.x, mouse.y, 5);
            }
            mouse.lastX = mouse.x;
            mouse.lastY = mouse.y;
            frameWorkTime = performance.now() - then;
        }

更新 sn-p

在下面的 cmets 中询问的是可重现的 HTML 文档。

注意此示例不能托管在 CodePen 或 StackOverflow 等站点中,因为它们会修改监视器或执行干扰测试的添加代码源

<!DOCTYPE html>
<html>
    <head><meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-8"></head>
    <body><script> 
    "use strict";
    (() => {
        var renderList = [], stop = false, i, ctx;
        requestAnimationFrame(function update(timer) { // Main loop
            if (!stop) { requestAnimationFrame(update) }
            for (i = 0; i < renderList.length; i ++){
                renderList[i](timer, ctx, w, h);            
            }
        });
    })();
    </script></body>
</html>

运行上述示例后,堆在调用主 GC 之前增长超过 60 秒,堆增长大约为每帧约 300 字节。


【问题讨论】:

  • 在配置文件 -> 堆快照、分配时间线和分配配置文件中,您可以找到准确分配的内容和原因。 PS:它更像是 V8 堆,因为 JS 本身没有堆和 GC 的概念。
  • 我意识到已经过去了很长时间,但是如果您创建了一个可运行的 sn-p 或重现该问题的 codepen(或类似的东西)会很有帮助。
  • 如果我不得不猜测我会说requestAnimationFrame(update) 分配了某种形式的闭包。
  • 您能否提供一个完整运行的示例来说明该问题,例如。在 jsfiddle 中?
  • @Kaiido 奇怪的是性能监视器(开发工具)上的开发工具选项 disable async stack trace 停止显示 JS 堆大小增长。但是,当使用performance tab 记录时,堆增长仍然存在。会不会是开发工具的开销?也许

标签: javascript v8


【解决方案1】:

您的代码中似乎没有明确的内存分配,这意味着它以其他方式发生 - 我看到您使用了一些第三方库。

您可以尝试在 GC 之前和之后拍摄内存快照(转到 devtools:内存,按下红色按钮)。

快照具有类名、这些类的对象数和占用的内存大小。

所以你得到 2 张快照,计算一个差异(不知何故),看看它是否适合你的这张锯形图片。

【讨论】:

    【解决方案2】:

    每次调用更新函数时,如果没有别的,变量 i 被创建然后销毁。我不知道 Javascript 是否会优化它并为 i 保留相同的存储位置,但如果不是,这是一种可能性。

    另一种可能性是从renderList[] 取消引用的任何函数都可能创建和/或销毁变量。

    如前所述,还有requestAnimationFrame() 函数可以创建/销毁变量。

    这些都是怀疑(而不是猜测),但根据您提供的数据,这就是可能的。正如其他人所提到的,为了进行全面调查,需要一个可重复的示例。

    【讨论】:

    • i 存储在堆栈中,因此不会溢出到堆中。但是,如果 i 是一个数组或对象,这将不是真的。
    猜你喜欢
    • 2011-03-23
    • 2021-12-08
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2012-07-22
    • 2023-04-03
    • 1970-01-01
    • 2021-02-24
    相关资源
    最近更新 更多