I am currently creating a Multiplayer Game using node.js and web sockets. The client side is receiving the positional information and applying it to all the players on the client side. Each client stores a list of all game elements. The main issue I am experiencing at the moment is a large amount of Garbage Collection. I am unsure what could be causing this, since I followed all of the tips I have found online; Set Variables to null to make sure they are collected asap. Dont create objects during runtime etc.After running a profile, this is the result:As you can see, when the GC kicks in, the game stops for a long time.How can I reduce/remove GC and object creation from my code. It is fully working, but it performs poorly on low end devices.I initialize all the arrays at the start.var energies = [];var walls = [];var users = [];var leaderboard = [];Those arrays are then filled with data by the server:socket.on('serverTellPlayerMove', function(userData, strEnergy) {    var visible = [];    for (var i = 0; i < userData.length;) {        if ((userData[i] == 1) == true) {            visible.push({                p : userData[0 + i] == 1,                score : userData[1 + i],                x : userData[2 + i],                y : userData[3 + i],                angle : userData[4 + i],                hue : userData[5 + i],                name : userData[6 + i],                dead : userData[7 + i] == 1,                dying : userData[8 + i] == 1            });            i += 9;        } else {            visible.push({                x : userData[0 + i],                y : userData[1 + i],                angle : userData[2 + i],                hue : userData[3 + i],                name : userData[4 + i],                dead : userData[5 + i] == 1,                dying : userData[6 * i] == 1            });            i += 7;        }    }    // SET DATA:    for (var i = 0; i < visible.length; i++) {        if (visible[i].p) {            var playerData = visible[i];            i = visible.length;        }    }    if (userData != ) {        if (playerType == 'player') {            // HOW MUCH YOU MOVED BY:            var xoffset = player.x - playerData.x;            var yoffset = player.y - playerData.y;            player.x = playerData.x;            player.y = playerData.y;            player.angle = playerData.angle;            player.hue = playerData.hue;            player.xoffset = isNaN(xoffset) ? 0 : xoffset;            player.yoffset = isNaN(yoffset) ? 0 : yoffset;            player.dead = playerData.dead;            document.getElementById(killsText).innerHTML = Score:                     + playerData.score;        }    }    // DEATH ANIM:    for (var i = 0; i < visible.length; i++) {        if (visible[i].dying) {            createExplosion(visible[i].x, visible[i].y);        }    }    // DEATH ANIM:    for (var i = 0; i < visible.length; i++) {        if (visible[i].dying) {            createExplosion(visible[i].x, visible[i].y);        }    }    users = visible;    visible = null;    var energiesList = [];    for (var i = 0; i < strEnergy.length; i += 5) {        energiesList.push({            x : strEnergy[0 + i],            y : strEnergy[1 + i],            index : strEnergy[2 + i],            animate : strEnergy[3 + i] == 1,            hue : strEnergy[4 + i]        });    }    var energyT = null;    for (var i = 0; i < energiesList.length; ++i) {        energyT = energiesList[i];        if (energyT.animate) {            energyT.animScale = 0;        } else {            if (energies[energyT.index] != undefined)                energyT.animScale = energies[energyT.index].animScale;        }        energies[energyT.index] = energyT;    }    energiesList = null;    energyT = null;    userData = null;    strEnergy = null;});This code is called 30 times a second. This updates every users data on each client side. It also updates the pellet data. Pellets are objects that can be picked up by players in the game. The above snippet does most of the logical work on the client side. Each object is also rendered as follows:window.requestAnimFrame = (function() {return window.requestAnimationFrame || window.webkitRequestAnimationFrame        || window.mozRequestAnimationFrame        || window.msRequestAnimationFrame || function(callback) {            window.setTimeout(callback, 1000 / 60);        };})();function animloop() {    gameLoop();    animLoopHandle = window.requestAnimFrame(animloop);}function doGame() {    now = Date.now();    delta = now - lastUpdate;    lastUpdate = now;    startX = player.x - (screenWidth / 2);    startY = player.y - (screenHeight / 2);    graph.fillStyle = backgroundColor;    graph.fillRect(0, 0, screenWidth, screenHeight);    drawgrid();    drawEnergies(energies);    drawPlayers(users);    drawWalls(walls);}And the actual rendering:function drawPlayers(playersToDraw) {    var playerC = null;    var fontSize = Math.max(playerRadius / 1.2, 11);    var radiusD = (playerRadius * 2.0);    for (var z = 0; z < playersToDraw.length; z++) {        playerC = playersToDraw[z];        if (!playerC.dead) {            var shapeX = playerC.x - startX;            var shapeY = playerC.y - startY;            var rotation = (Math.PI / 180) * playerC.angle;            scale = radiusD;            graph.fillStyle = 'hsl(' + playerC.hue + ', 50%, 45%)';            graph.strokeStyle = 'hsl(' + playerC.hue + ', 50%, 60%)';            graph.lineJoin = round;            graph.lineWidth = playerHRadius;            graph.save();            graph.translate(shapeX, shapeY);            graph.rotate(rotation);            graph.beginPath();            graph.moveTo(0, (-2 * scale / 3));            graph.lineTo((-scale / 2), scale / 3);            graph.lineTo((scale / 2), scale / 3);            graph.lineTo(0, (-2 * scale / 3));            graph.closePath();            graph.stroke();            graph.fill();            graph.restore();        }    }    playerC = null;}As you can see I am setting playerC to null in an Attempt to make it be collected by the GC.There are some more rendering functions as well, but they are all very similar to the player draw function. How do I reduce GC on this code and does the data received from the server need to be collected as well?I have attempted to implement v-sync, but then I realized that the movement data is sent by the server, to there is no way to use delta time or v-sync.As suggested, I did a Heap Snapshot. Here is the result:And here is what was actually removed:After some further looking around, I found this:It points at the data received from the server. Also I noticed some throw TypeError statements, what could be causing that?Here is a LIVE DEMO of the code in action:http://www.vertix.io
Garbage collection in JavaScript multiplayer game
javascript;performance;node.js;memory management;animation
I think you should use a static approach for the visible array, avoiding the push instruction: declare and fill the array with dummy structures outside the socket.on handler and then iterate over the items to change their value. Use a variable to keep the length of the received values, instead of calling visible.length().You are also copying many times your data: in Javascript var playerData = visible[i]; makes another copy of the data, consider accessing the player's data by index instead of copying it in another structure.The same problem arises for the energiesList array. Define it outside the event and only update its values inside, keeping the real lenght in a variable.Hope it helps.EDITThis is how the code should be refactored (you need to test it)MAX_VISIBLES = 500;VISIBLE_PLAYER = 1;VISIBLE_OTHER = 0;var visible = [];for (var i = 0; i < MAX_VISIBLES; i++) {    visible.push({        type: 0,        p : 0,        score : 0,        x : 0,        y : 0,        angle : 0,        hue : 0,        name : 0,        dead : 0,        dying : 0    });}// Just need it once (only if you use it anywhere else in the code)users = visible;// You'll need it if you want to iterate over usersvar usersLength = 0;MAX_ENERGIES = 1000;var energiesList = [];for (var i = 0; i < MAX_ENERGIES; i++) {    energiesList.push({        x : 0,        y : 0,        index : 0,        animate : 0,        hue : 0    });}socket.on('serverTellPlayerMove', function(userData, strEnergy) {    for (var i = 0, visibleLength = 0; i < userData.length; visibleLength++) {        if ((userData[i] == 1) == true) {            visible[visibleLength].type = VISIBLE_PLAYER;            visible[visibleLength].p : userData[0 + i] == 1;            visible[visibleLength].score : userData[1 + i];            visible[visibleLength].x : userData[2 + i];            visible[visibleLength].y : userData[3 + i];            visible[visibleLength].angle : userData[4 + i];            visible[visibleLength].hue : userData[5 + i];            visible[visibleLength].name : userData[6 + i];            visible[visibleLength].dead : userData[7 + i] == 1;            visible[visibleLength].dying : userData[8 + i] == 1;            i += 9;            var playerData = visible[visibleLength];        } else {            visible[visibleLength].type = VISIBLE_OTHER;            visible[visibleLength].x : userData[2 + i];            visible[visibleLength].y : userData[3 + i];            visible[visibleLength].angle : userData[4 + i];            visible[visibleLength].hue : userData[5 + i];            visible[visibleLength].name : userData[6 + i];            visible[visibleLength].dead : userData[7 + i] == 1;            visible[visibleLength].dying : userData[8 + i] == 1;            i += 7;        }    }// I don't think you need this cycle: playerdata can be assigned// during userData parsing//    // SET DATA://    for (var i = 0; i < visible.length; i++) {//        if (visible[i].p) {//            var playerData = visible[i];//            i = visible.length;//        }//    }    if (userData != ) {        if (playerType == 'player') {            // HOW MUCH YOU MOVED BY:            var xoffset = player.x - playerData.x;            var yoffset = player.y - playerData.y;            player.x = playerData.x;            player.y = playerData.y;            player.angle = playerData.angle;            player.hue = playerData.hue;            player.xoffset = isNaN(xoffset) ? 0 : xoffset;            player.yoffset = isNaN(yoffset) ? 0 : yoffset;            player.dead = playerData.dead;            document.getElementById(killsText).innerHTML = Score:                     + playerData.score;        }    }    // DEATH ANIM:    for (var i = 0; i < visibleLength; i++) {        if (visible[i].dying) {            createExplosion(visible[i].x, visible[i].y);        }    }    // DEATH ANIM:    for (var i = 0; i < visibleLength; i++) {        if (visible[i].dying) {            createExplosion(visible[i].x, visible[i].y);        }    }// See above//    users = visible;//    visible = null;    usersLength = visibleLength;    for (var i = 0, energiesListLength = 0; i < strEnergy.length; i += 5, energiesListLength++) {        energiesList[energiesListLength].x : strEnergy[0 + i];        energiesList[energiesListLength].y : strEnergy[1 + i],        energiesList[energiesListLength].index : strEnergy[2 + i];        energiesList[energiesListLength].animate : strEnergy[3 + i] == 1;        energiesList[energiesListLength].hue : strEnergy[4 + i];    }    var energyT = null;    for (var i = 0; i < energiesListLength; ++i) {        energyT = energiesList[i];        if (energyT.animate) {            energyT.animScale = 0;        } else {            if (energies[energyT.index] != undefined)                energyT.animScale = energies[energyT.index].animScale;        }        energies[energyT.index] = energyT;    }// You don't need to force GC: you need to avoid GC//    energyT = null;//    userData = null;//    strEnergy = null;});