Search code examples
javascriptnode.jsperformancegoogle-chromev8

Why node.js is much slower than Google Chrome?


I've got simple code, that creates a chunk with 16*16*256 boxes of 10 elements array and a bool variable. Method tick increases array elements and changes boolean value for each box in a chunk 100 times.

By measuring time I've got the results:

Windows x64 node.js 7.4.0:
1 tick: 102.85
Total time(100): 10285

Google Chrome 55 on the same machine:
1 tick: 18.08
Total time(100): 1808

Even underlying snippet is an order of magnitude faster than a Node.
Snippet:
1 tick: 22.79
Total time(100): 2279

So, how can I make Node work faster?

(function(){
	class Box {
		constructor() {
			this.data = new Array(10); 
			this.state = false;
		}

		tick() {
			this.state = !this.state;
			for(let i = 0; i < this.data.length; i++) {
				this.data[i]++;
			}
		}
	}

	class Chunk {
		constructor() {
			this.data = new Array(256);

			for(let z = 0; z < this.data.length; z++) {
				this.data[z] = new Array(16);

				for(let x = 0; x < this.data[z].length; x++) {
					this.data[z][x] = new Array(16);

					for(let y = 0; y < this.data[z][x].length; y++) {
						this.data[z][x][y] = new Box();
					}
				}
			}
		}

		tick() {
			for(let z = 0; z < this.data.length; z++) {
				for(let x = 0; x < this.data[z].length; x++) {
					for(let y = 0; y < this.data[z][x].length; y++) {
						this.data[z][x][y].tick();
					}
				}
			}
		}
	}



	var c = new Chunk();
	var count = 100;
	var start = new Date().getTime();

	for(let i = 0; i < count; i++) {
		c.tick();
	}

	var end = new Date().getTime();

	console.log("1 tick: " + (end - start) / count);
	console.log("Total time(" + count + "): " + (end - start));
})();


Solution

  • Pre-filling the arrays gives an order of magnitude improvement in node

    The only explanation I can give is that the "conversion" of an empty array slot to a "used" slot is more expensive in nodejs - you may find similar speed up by simply "filling" the array from length-1 to 0 in your original code (not quite as much but close)

    The major speed up is the pre-fill of this.data in class Box (but that's probably because this.data[i]++ on an empty array item is used 655360 times)

    (function(){
        class Box {
            constructor() {
                this.data = new Array(10).fill(0); 
                this.state = false;
            }
    
            tick() {
                this.state = !this.state;
                for(let i = 0; i < this.data.length; i++) {
                    this.data[i]++;
                }
            }
        }
    
        class Chunk {
            constructor() {
                this.data = new Array(256).fill(null);
    
                for(let z = 0; z < this.data.length; z++) {
                    this.data[z] = new Array(16).fill(null);
    
                    for(let x = 0; x < this.data[z].length; x++) {
                        this.data[z][x] = new Array(16).fill(null);
    
                        for(let y = 0; y < this.data[z][x].length; y++) {
                            this.data[z][x][y] = new Box();
                        }
                    }
                }
            }
    
            tick() {
                for(let z = 0; z < this.data.length; z++) {
                    for(let x = 0; x < this.data[z].length; x++) {
                        for(let y = 0; y < this.data[z][x].length; y++) {
                            this.data[z][x][y].tick();
                        }
                    }
                }
            }
        }
    
    
    
    
    
        var c = new Chunk();
        var count = 100;
        var start = new Date().getTime();
    
        for(let i = 0; i < count; i++) {
            c.tick();
        }
    
        var end = new Date().getTime();
    
        console.log("1 tick: " + (end - start) / count);
        console.log("Total time(" + count + "): " + (end - start));
    })();
    

    as an aside, your Chunk constructor can be "simplified" to

    this.data = new Array(256).fill([]).map(item => new Array(16).fill([]).map(item => new Array(16).fill([]).map(item => new Box())));
    

    But this does not make any further improvement

    I also tried using .forEach and/or .map where appropriate - but any such change actually slowed down the tick by about 10% in node