Search code examples
javaarraysfor-loopiteration2d-games

Java saving 2d tiles: for loop getting slower with each iteration up to 5 seconds for 10000 iterations


I'm currently developing a 2d game based on tiles. At the moment, I'm writing a process to save the whole world. In general, a world is 10000 x 10 = 1.100.000 blocks big. Obviously, it shouldn't be a problem for java, but it is.

Here's my loop:

Material[][] blocks = g.gameState.getLevel().tiles;
int y = 0;
int blocksHor = g.gameState.getLevel().getBlocksHor();

for(int x = 0; x < blocksHor; x++) {

    world += blocks[y][x] + " ";

    if(x >= blocksHor - 1) {
        x = 0;
        y++;
        System.out.println("LAYER " + y + " => " + (System.currentTimeMillis() - now) + " ms");
        now = System.currentTimeMillis();
    }
}

The loop is a bit hard to understand, but I already tried to optimize wherever I could. Here's the console output:

LAYER 1 => 86 ms
LAYER 2 => 276 ms
LAYER 3 => 424 ms
LAYER 4 => 618 ms
LAYER 5 => 561 ms
LAYER 6 => 541 ms
LAYER 7 => 755 ms
LAYER 8 => 885 ms
LAYER 9 => 1036 ms

These processes due up to 50 seconds and more at the end.

What's wrong with this loop? I honestly don't know how I should solve this problem.

Thank you :) -phil


Solution

  • If indeed the code you presented is representative of your problem, then the issue is very likely at

    world += blocks[y][x] + " ";
    

    There are several performance issues with that. In the first place, world must have type String, and Strings are immutable. Therefore, whenever you concatenate them via the + operator, a new one must be created to hold the result. You are doing that at least twice in each execution of that statement, producing overall in excess of 2M garbage objects that must eventually be collected.

    Moreover, the intermediate values of world increase steadily in size, so the combined memory consumed by all those temporaries is proportional to the square of the total number of blocks. Even if the string value of each block were only one character long, this would amount to a great deal of memory. You're going to end up with serious garbage collection issues, and they will indeed get steadily worse as you proceed.

    As a first attempt at improving this, you could consider accumulating the data into a StringBuilder instead of a String:

    StringBuilder worldBuilder = new StringBuilder();
    
    // ...
    
    worldBuilder.append(blocks[y][x]).append(' ');
    

    That should significantly mitigate the GC issues by creating much less garbage, but you'll still end up requiring a pretty large object to hold the whole state. Therefore, I'd also suggest accumulating the data into smaller chunks, outputting them as you go, instead of constructing the whole representation in memory before outputting anything.

    You might also want to look at the implementation of Material.toString(), for if it, too, produces a lot of garbage by performing string concatenations then that will magnify the problem.