1

I am using node.js and socket.io to run a multiplayer game I have made. It basically loops 30/sec and sends the data to all clients.

I only send the necessary data for each client to the way I built my app was: - When a socket connects I store it on a global object - At the end of the every loop I loop through this object and I send the necessary that for each socket (data about players and other objects around him).

I am not having any network problems I was able to make this game run with low bandwidth usage but to reduce the bandwidth usage I had to choose to use more CPU.

Once I have around 40-50 players online I use about 50% of my CPU so the game becomes very slow. I would not really care if I could make it use 50% of the CPU without making the game slow if it is possible and anyone knows how to do that please tell me. I had created a CPU profile and for what I see the program is about 72% the time idle and the functions the consume more time are all from Socket.io so I can´t really improve them.

I see that games like agar.io can handle 500 player per room, I would like to handle 100-150.

Is socket.io using too much CPU because I loop through every socket and use socket.emit() for each one instead of using io.emit() and send the same data to everyone?

What could I do to improve my server performance and reduce the CPU usage without having to consume more bandwith?

I am running Ubuntu 16.04 LTS.

Part of my server CPU profile (ordered by Self Time):

Self time - Total time - Function

10947.3 ms  10947.3 ms  (idle)(program):1   
578.8 ms 14.28 %    612.1 ms 15.10 %    _hasBinary/home/daniel/server/node_modules/socket.io/node_modules/has-binary/index.js:25    
559.0 ms 13.79 %    559.0 ms13.79 % write(program):1    
375.8 ms 9.27 % 375.8 ms9.27 %  encodeAsString/home/daniel/server/node_modules/socket.io/node_modules/socket.io-parser/index.js:147 
220.7 ms 5.45 % 220.7 ms 5.45 % writeBuffer(program):1  
106.2 ms 2.62 % 106.2 ms 2.62 % (program)(program):1    
83.3 ms 2.05 %  83.3 ms 2.05 %  self.inSameWindow/home/daniel/server/app.js:370 
78.1 ms 1.93 %  152.0 ms 3.75 % Bufferbuffer.js:47  
71.8 ms 1.77 %  1238.8 ms 30.57 %   Socket.emit/home/daniel/server/node_modules/socket.io/lib/socket.js:126 
66.6 ms 1.64 %  250.9 ms 6.19 % self.updateSpeed/home/daniel/server/app.js:332  
65.6 ms 1.62 %  65.6 ms 1.62 %  self.downBumper/home/daniel/server/app.js:145   
64.5 ms 1.59 %  64.5 ms 1.59 %  self.rightBumper/home/daniel/server/app.js:142  
64.5 ms 1.59 %  64.5 ms 1.59 %  self.leftBumper/home/daniel/server/app.js:139   
59.3 ms 1.46 %  59.3 ms 1.46 %  self.upBumper/home/daniel/server/app.js:136 
58.3 ms 1.44 %  172.8 ms 4.26 % self.fixOverCollision/home/daniel/server/app.js:151 
58.3 ms 1.44 %  58.3 ms 1.44 %  self.updateSquat/home/daniel/server/app.js:323  
58.3 ms 1.44 %  58.3 ms 1.44 %  self.updatePosition/home/daniel/server/app.js:59    
55.2 ms 1.36 %  55.2 ms 1.36 %  self.updateSprite/home/daniel/server/app.js:305 
55.2 ms 1.36 %  55.2 ms 1.36 %  self.getUpdatePack/home/daniel/server/app.js:394    
51.0 ms 1.26 %  51.0 ms 1.26 %  addListenerevents.js:191    
51.0 ms 1.26 %  2026.9 ms 50.01 %   (anonymous function)/home/daniel/server/app.js:681  
39.6 ms 0.98 %  39.6 ms 0.98 %  byteLengthUtf8(program):1   
39.6 ms 0.98 %  143.7 ms 3.54 % readableAddChunk_stream_readable.js:123 
34.4 ms 0.85 %  153.0 ms 3.78 % emitNoneevents.js:65    
34.4 ms 0.85 %  34.4 ms 0.85 %  (garbage collector)(program):1  
32.3 ms 0.80 %  254.0 ms 6.27 % createWriteReqnet.js:697    
31.2 ms 0.77 %  42.7 ms 1.05 %  Buffer.writebuffer.js:523   
30.2 ms 0.74 %  78.1 ms 1.93 %  Readable.on_stream_readable.js:664  
29.1 ms 0.72 %  30.2 ms 0.74 %  nextTicknode.js:477 
29.1 ms 0.72 %  678.7 ms 16.75 %    PerMessageDeflate.compress/home/daniel/server/node_modules/socket.io/node_modules/engine.io/node_modules/ws/lib/PerMessageDeflate.js:289    
28.1 ms 0.69 %  34.4 ms 0.85 %  module.exports.collidingBlock/home/daniel/server/server/map.js:213  
27.1 ms 0.67 %  372.7 ms 9.20 % emitevents.js:117   
26.0 ms 0.64 %  26.0 ms 0.64 %  slice

This is how I loop through every socket:

 //LOOP
var initPack = {player:[], bullet:[]};
var removePack = {player:[], bullet:[]};
setInterval(function(){
    var pack = {
        player: Player.update(),
        bullet: Bullet.update()
    };
    for(var i in SOCKET_LIST){
        var socket = SOCKET_LIST[i];
        var socket_pack = {
            player:[], 
            bullet:[]
        };

        if(initPack.player.length > 0 || initPack.bullet.length > 0)
            socket.emit("init", initPack);

        //Get the pack for specific socket
        if(typeof Player.list[i] != "undefined"){
            var player = Player.list[i];
            //Get players around
            for(var a = 0; a < pack.player.length; a++){
                if(player.inSameWindow(pack.player[a].x, pack.player[a].y))
                    socket_pack.player.push(pack.player[a]);
            }
            //Get bullets around
            for(var a = 0; a < pack.bullet.length; a++){
                if(player.inSameWindow(pack.bullet[a].x, pack.bullet[a].y))
                    socket_pack.bullet.push(pack.bullet[a]);
            }
        }
        socket.emit("update", socket_pack);

        if(removePack.player.length > 0 || removePack.bullet.length > 0)
            socket.emit("remove", removePack);

        if(Game.widgetDataChanged){
            socket.emit("widget", Game.getWidgetPack());
        }
    }
    Game.widgetDataChanged = false;
    initPack.player = [];
    initPack.bullet = [];
    removePack.player = [];
    removePack.bullet = [];

}, 1000/30);
Cœur
  • 37,241
  • 25
  • 195
  • 267
Daniel Oliveira
  • 1,280
  • 14
  • 36
  • 2
    The usual solution to using too much CPU is to profile your code to find the largest bottlenecks and then develop strategies to address those particular issues. Optimizing without measurement first is generally a fool's errand because your guess about what is taking the most time is usually wrong and thus just results in wasted effort optimizing the wrong thing. FYI, just doing basic math, serving hundreds of users 30 times per second with one node.js process requires using less than a few ms per user on each cycle. – jfriend00 Aug 13 '16 at 02:29
  • You have to either get really, really fast at doing a cycle (which probably means not sending data to most users on each cycle), you have to execute fewer cycles per second and/or you have to involve many processes/CPUs to share the load. – jfriend00 Aug 13 '16 at 02:32
  • I have profiled my code! But the most expensive functions are from socket.io library and I cant do much about that. Now you made me think that not sending data to most users on each cycle can be a soluction. About involving many processes/CPU can It be done on nodejs? – Daniel Oliveira Aug 13 '16 at 02:57
  • Yes, it appears you are sending too often to too many clients. Reducing that will help a lot. The clustering module allows you to use many processes. To use socket.io with clustering, you also probably need the redis adapter for socket.io. – jfriend00 Aug 13 '16 at 03:02
  • 1
    I found out that using passing binary data through Websockets instead of JSON maybe be a solution but I don´t really know how does websockets/socket.io works with binary data. Does anyone here know a good tutorial? I could not find much on google... – Daniel Oliveira Aug 14 '16 at 01:52
  • 1
    [How to send binary data with socket.io](http://stackoverflow.com/questions/24071561/how-to-send-binary-data-from-a-node-js-socket-io-server-to-a-browser-client) – jfriend00 Aug 14 '16 at 02:37
  • 3
    If you're not sending binary data, remove all the code inside the `hasBinary` function and simply replace it with `return false`. That made a huge difference for me. – Thomas Wagenaar Oct 28 '17 at 20:19

0 Answers0