SOLVED instance_create_layer() seems incredibly slow in bulk

So, I recently rewrote my game to use my own system instead of the built-in Room system so I could
A) more easily transition between them without weird workarounds for the fact Rooms don't load until after you finish executing all your code
B) use an external map editor to create them
C) add custom properties to rooms, like names and whatnot

So I wrote a script to load all the map tiles/maps into objects and store them in Game Maker, and then another script to clear out the room and fill it with objects (mostly trees at the moment) and tiles. The issue is, a room with a size of 75x75 loads incredibly slowly through this method (multiple seconds), and I have found that is because it's calling instance_create_layer about 5372 times, and instance_create_layer is absurdly slow.

For reference, each of those objects has to instantiate a ds_list each time it's created, which is the second-most-taxing part of loading the area, and that process takes only about 33ms, where the process of creating the instances themselves is taking 5757ms (almost two seconds!). Considering that I'm providing the function with an x-coordinate, a y-coordinate, the layer ID, and the object ID, it should NOT be taking this long.

room_layers[layer_id][y_coordinate][x_coordinate] = object_id. Done. That's all that should be happening there. Someone explain to me how YoYo Games justifies this function taking over a third of a millisecond to complete on average, and if there's any way at all for me to work around it.
 

chamaeleon

Member
So, I recently rewrote my game to use my own system instead of the built-in Room system so I could
A) more easily transition between them without weird workarounds for the fact Rooms don't load until after you finish executing all your code
B) use an external map editor to create them
C) add custom properties to rooms, like names and whatnot

So I wrote a script to load all the map tiles/maps into objects and store them in Game Maker, and then another script to clear out the room and fill it with objects (mostly trees at the moment) and tiles. The issue is, a room with a size of 75x75 loads incredibly slowly through this method (multiple seconds), and I have found that is because it's calling instance_create_layer about 5372 times, and instance_create_layer is absurdly slow.

For reference, each of those objects has to instantiate a ds_list each time it's created, which is the second-most-taxing part of loading the area, and that process takes only about 33ms, where the process of creating the instances themselves is taking 5757ms (almost two seconds!). Considering that I'm providing the function with an x-coordinate, a y-coordinate, the layer ID, and the object ID, it should NOT be taking this long.

room_layers[layer_id][y_coordinate][x_coordinate] = object_id. Done. That's all that should be happening there. Someone explain to me how YoYo Games justifies this function taking over a third of a millisecond to complete on average, and if there's any way at all for me to work around it.
You do realize the create event is executed as part of instance create calls, right? instance_create_layer() and instance_create_depth() are not done until your own Create event code has executed. Adding 100,000 instances seemed to take about 0.2 seconds for a single run for me, if the instances don't do anything in their create event. If they do, well, then the amount of time would be depending on the work required.
 
You do realize the create event is executed as part of instance create calls, right? instance_create_layer() and instance_create_depth() are not done until your own Create event code has executed. Adding 100,000 instances seemed to take about 0.2 seconds for a single run for me, if the instances don't do anything in their create event. If they do, well, then the amount of time would be depending on the work required.
Yes, I realize that, and commenting out the create event code was the first thing I tried. It made no difference. The 33ms is the total amount of time for every call, so the delay isn't coming from there. However, when I commented out instance_create_layer entirely, the process was near-instantaneous.

As a result of this issue, I tried creating all the objects at once and then deactivating them until the zone loaded, and then reactivating them as needed. This, somehow, took even longer (despite ideally just being flipping an "active" variable on the backend, and at worst being adding/removing from a list); up to almost 4 seconds. So don't tell me it's my code.
 
I've generated many, many different procedural worlds and have not experienced the kind of slowdown you are talking about in reference to instance creation. Do you really need to use instances or can you substitute a data structure (for instance, a map or a grid) that stores the data you need instead (you can use tiles in place of the instances and manipulate them based on the data in your data structure to simulate "instance interactivity")? Does each instance really need to create a ds_list, or can you farm that out to another data structure?

In any case, sometimes generating stuff takes time (for instance, using GMS to make proper perlin noise sucks, it's much better farmed out to a shader). If there really is a problem with how many instances you need to create, then program in a loading screen while you are generating your world.
 

Mythi.T

Member
Hello

While I have no clue on what happens in the GMS background, I've got a solution suggestion to give to you.
  • Loading screen. Create some instances which at room start spends more time loading a lot less instances per frame, say 25 per frame. Have an instance cover the camera so you don't see the world building in front of your eyes. A simple "Loading Level" text in the center of the screen and another instance which controls world generation (both which would stay active and visible until those 5k instances are generated). For a fancy addition, have a text line display the amount of instances that have been loaded, or a percentage reading displaying how much of the loading has occurred. This isn't that difficult to do, it's just dispersing instance creation over multiple frames.
 

chamaeleon

Member
Yes, I realize that, and commenting out the create event code was the first thing I tried. It made no difference. The 33ms is the total amount of time for every call, so the delay isn't coming from there. However, when I commented out instance_create_layer entirely, the process was near-instantaneous.

As a result of this issue, I tried creating all the objects at once and then deactivating them until the zone loaded, and then reactivating them as needed. This, somehow, took even longer (despite ideally just being flipping an "active" variable on the backend, and at worst being adding/removing from a list); up to almost 4 seconds. So don't tell me it's my code.
GML:
t1 = current_time;

for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
}

t2 = current_time;

show_debug_message("Creation time = " + string((t2-t1)/1000));
Code:
Creation time = 0.20
 
I've generated many, many different procedural worlds and have not experienced the kind of slowdown you are talking about in reference to instance creation. Do you really need to use instances or can you substitute a data structure (for instance, a map or a grid) that stores the data you need instead (you can use tiles in place of the instances and manipulate them based on the data in your data structure to simulate "instance interactivity")? Does each instance really need to create a ds_list, or can you farm that out to another data structure?

In any case, sometimes generating stuff takes time (for instance, using GMS to make proper perlin noise sucks, it's much better farmed out to a shader). If there really is a problem with how many instances you need to create, then program in a loading screen while you are generating your world.
I don't know if I need to use instances but I don't see how else I could be changing the objects on the map. I created my own tile system and that's what I'm pulling the data from to create the instances with.

To be perfectly frank, though, if the solution you're suggesting to me is "don't engage with Rooms or Instances at all, just program your own structures and handle them there" then I'm going to chargeback YoYo Games at my bank and switch to Godot.

Hello

While I have no clue on what happens in the GMS background, I've got a solution suggestion to give to you.
  • Loading screen. Create some instances which at room start spends more time loading a lot less instances per frame, say 25 per frame. Have an instance cover the camera so you don't see the world building in front of your eyes. A simple "Loading Level" text in the center of the screen and another instance which controls world generation (both which would stay active and visible until those 5k instances are generated). For a fancy addition, have a text line display the amount of instances that have been loaded, or a percentage reading displaying how much of the loading has occurred. This isn't that difficult to do, it's just dispersing instance creation over multiple frames.
A loading screen is a poor solution IMO. It simply shouldn't take this long to perform a task of this magnitude. I am not doing anything particularly demanding; if this is what the game is performing like now it can only get worse from here.

GML:
t1 = current_time;

for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
}

t2 = current_time;

show_debug_message("Creation time = " + string((t2-t1)/1000));
Code:
Creation time = 0.20
I ran that code and got this:
Creation time = 0.28

So I must be doing something wrong, though I can't imagine what because there really is nothing terribly complex about my code. I'll keep looking.
 
I don't know if I need to use instances but I don't see how else I could be changing the objects on the map. I created my own tile system and that's what I'm pulling the data from to create the instances with.

To be perfectly frank, though, if the solution you're suggesting to me is "don't engage with Rooms or Instances at all, just program your own structures and handle them there" then I'm going to chargeback YoYo Games at my bank and switch to Godot.
Efficiency and optimization is not something that magically comes from any particular IDE. Part of game programming in any language and any environment is being able to be resourceful and clever in your use of the tools at hand. Just because GMS is generally easy to use for newcomers and has many features that help support people who aren't necessarily brilliant coders doesn't mean that all problems then should be easy.

And as I said, I doubt that it's "GMS being super slow creating instances" as I've created many different proc-gen projects and toyed around with the generation side of game dev a lot and have never run into any problems that weren't my own doing. Might I suggest calming your responses a bit, as the problem you are facing is far more likely to be caused by a miscalculation or misunderstanding on your part than it is GMS genuinely being extremely slow.
 
Efficiency and optimization is not something that magically comes from any particular IDE. Part of game programming in any language and any environment is being able to be resourceful and clever in your use of the tools at hand. Just because GMS is generally easy to use for newcomers and has many features that help support people who aren't necessarily brilliant coders doesn't mean that all problems then should be easy.

And as I said, I doubt that it's "GMS being super slow creating instances" as I've created many different proc-gen projects and toyed around with the generation side of game dev a lot and have never run into any problems that weren't my own doing. Might I suggest calming your responses a bit, as the problem you are facing is far more likely to be caused by a miscalculation or misunderstanding on your part than it is GMS genuinely being extremely slow.
Sorry, but this is extremely frustrating. I'm moderating my responses as much as I can. And if the tools GMS2 gives me are genuinely this sloppy and inefficient, I'd rather take back my money and make my own.

I went back and disabled the Create code in all of the instances I'm creating just to be sure. Here's the Profiler output:

ISOMMlz.png

As you can see, 2 full seconds just to create the 5585 objects. Unacceptable. So I tried switching it to obj_foo from earlier instead of the object I'm creating...

Tcq2XiS.png
I dunno what to tell ya. Here's the code I'm using. Maybe you can see what I'm missing.

GML:
var zone_id = argument0;
var zone = ds_map_find_value(global.zones, zone_id);
var sh = 64;
var sw = 64;
var asset_cache = ds_map_create();
if zone != undefined
{
    global.zone = zone_id;
    zone.loading = true;
    //Cleanup old zone
    layer_destroy_instances(layer_get_id("ContextMenu"));
    layer_destroy_instances(layer_get_id("Speech"));
    layer_destroy_instances(layer_get_id("Instances"));
    var zone_height = ds_grid_height(zone.zone);
    var zone_width = ds_grid_width(zone.zone);
    //Resize room
    room_height = sh*zone_height;
    room_width = sw*zone_width;
    var tilemap = layer_tilemap_get_id(layer_get_id("Tiles"));
    tilemap_set_height(tilemap, zone_height);
    tilemap_set_width(tilemap, zone_width);
    var bg_id = layer_background_get_id(layer_get_id("Background"));
    var obj_layer = layer_get_id("Instances")
    layer_background_sprite(bg_id, ForestFloorSprite);
    layer_background_blend(bg_id, c_white);
    //Create objects and transition zones
    for(var i = 0; i < zone_width * zone_height; i++)
    {
        var yy = floor(i / zone_width);
        var xx = (i % zone_width);
        var tile = ds_grid_get(zone.zone, xx, yy);
        tilemap_set(tilemap, tile.bg, xx, yy);
        if tile.object != ""
        {
            if not ds_map_exists(asset_cache, tile.object)
            {
                ds_map_add(asset_cache, tile.object, asset_get_index(tile.object));
            }
            instance_create_layer(xx*sw, yy*sh, obj_layer, obj_foo);
        }
        //can this be done with less repetition?
        if yy == 0 and zone.area_north != ""
        {
            var transition = instance_create_layer(xx*sw, yy*sh, obj_layer, ZoneTransition);
            transition.transition_to = zone.area_north;
            transition.node_num = xx;
            transition.transition_x = 0;
            transition.transition_y = 1;
        }
        else if yy == zone_height-1 and zone.area_south != ""
        {
            var transition = instance_create_layer(xx*sw, yy*sh, obj_layer, ZoneTransition);
            transition.transition_to = zone.area_south;
            transition.node_num = xx;
            transition.transition_x = 0;
            transition.transition_y = -1;
        }
        else if xx == 0 and zone.area_west != ""
        {
            var transition = instance_create_layer(xx*sw, yy*sh, obj_layer, ZoneTransition);
            transition.transition_to = zone.area_west;
            transition.node_num = yy;
            transition.transition_x = 1;
            transition.transition_y = 0;
        }
        else if xx == zone_width-1 and zone.area_east != ""
        {
            var transition = instance_create_layer(xx*sw, yy*sh, obj_layer, ZoneTransition);
            transition.transition_to = zone.area_east;
            transition.node_num = yy;
            transition.transition_x = -1;
            transition.transition_y = 0;
        }
    }
    SpawnPlayerInNewZone(instance_find(Player,0));
    zone.loading = false;
    return true;
}
 

samspade

Member
What happens if you test chamaeleon's code in a blank project? Do you get a similar or different result. This would tell you whether it something relate to GM or your computer versus the project specific code you have.

I attempted it and got the following results: Creation time = 0.26. Pretty much in line. I've also done some pretty large scale instance creation stuff and never once had an issue with instance_create_layer's speed.

GML:
t1 = current_time;

for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
}

t2 = current_time;

show_debug_message("Creation time = " + string((t2-t1)/1000));
Also, how often are you running your code?
 
Last edited:
Update: I tried placing the same code from above in above the code I showed. It took much, much longer than before:

Creation time = 42.97

I am beginning to suspect my problem isn't with instance_create_object but rather with the method that I'm calling it from. Since this is a multiplayer game, the code above is called from an Async Networking Event. It's my suspicion that instance_create_layer takes much more time to finish when called from an asynchronous event than a normal one. I'm going to put together a workaround and test it out.

Also, how often are you running our code?
Once, when the player hits the transition boundary and receives the go-ahead to transition from the server.
 

chamaeleon

Member
@Unknown Entity I can't see anything in that code, but on the other hand, that code snippet is outside the scope of what instance_create_layer() does. I see some inheritance stuff going on. Anything in create events up the chain?
 
No luck, creating the instances still takes a long time even after I moved the function call from the Networking event to the Player object's Step event. This is getting pretty baffling to be honest.

@Unknown Entity I can't see anything in that code, but on the other hand, that code snippet is outside the scope of what instance_create_layer() does. I see some inheritance stuff going on. Anything in create events up the chain?
obj_foo was literally created for this purpose and is untouched, so no.
 

chamaeleon

Member
No luck, creating the instances still takes a long time even after I moved the function call from the Networking event to the Player object's Step event. This is getting pretty baffling to be honest.


obj_foo was literally created for this purpose and is untouched, so no.
I realize your profile output indicates ~5500 instances, but can you sanity check it by doing an instance_count() of them afterward or something?
 
I realize your profile output indicates ~5500 instances, but can you sanity check it by doing an instance_count() of them afterward or something?
Sure thing.

Creation time = 2.07
Number of obj_foo = 5435

So it's not that. Note that I added the loop code you posted to the very top of my function and it took 42 seconds. It must be something about my GML function.
 
To take you all the way through the process, here's how this works:

1. Player collides with the zone boundary
2. Player sends a network message to the server
3. Server reviews message and sends one back
4. Player receives message, processes it, and initiates zone transition (reset zone-specific variables on the player instance, and then call the zone-switch function)
5. Zone function runs the code shown above

So I can't see any extenuating circumstances that would cause this issue. I've filed a bug report with YoYo Games.
 

chamaeleon

Member
Sure thing.

Creation time = 2.07
Number of obj_foo = 5435

So it's not that. Note that I added the loop code you posted to the very top of my function and it took 42 seconds. It must be something about my GML function.
I finally realize the profiling graph's top level instance_create_level is not for the object you have replaced or experimented with its Create event. If Tree is the thing you swapped out for obj_foo in addition to not doing anything in the Create event, it looks like 5434 Tree instance_create_layer() calls only took 20ms, which means other code running as part of your "controller" instance Create event would be responsible.
 

chamaeleon

Member
Can you perhaps separate out the tile stuff and run it before the instance creation stuff?
GML:
    ...
    t1 = current_time;
    for(var i = 0; i < zone_width * zone_height; i++)
    {
        var yy = floor(i / zone_width);
        var xx = (i % zone_width);
        var tile = ds_grid_get(zone.zone, xx, yy);
        tilemap_set(tilemap, tile.bg, xx, yy);
        if tile.object != ""
        {
            if not ds_map_exists(asset_cache, tile.object)
            {
                ds_map_add(asset_cache, tile.object, asset_get_index(tile.object));
            }
        }
    }
    t2 = current_time;
    show_debug_message("Time = " + string((t2-t1)/1000));
    for(var i = 0; i < zone_width * zone_height; i++)
    {
        var yy = floor(i / zone_width);
        var xx = (i % zone_width);
        var tile = ds_grid_get(zone.zone, xx, yy);
        if tile.object != ""
        {
            instance_create_layer(xx*sw, yy*sh, obj_layer, obj_foo);
        }
        ...
    }
Edit: I don't suspect this to be the main responsible part. Since this code belongs in a script, I am actually leaning towards some code running before after the call to this script in the actual Create event.
 
Last edited:

Simon Gust

Member
Brief note:
- ds maps are slow
- dot operations are slow
try to keep out zone.zone for example outside the loop as a variable. If you absolutely need to use a dot operation and use it multiple times in the same iteration, make a shortcut variable.
Code:
var obj = tile.object;
if obj != ""
{
    if not ds_map_exists(asset_cache, obj)
    {
        ds_map_add(asset_cache, obj, asset_get_index(obj));
    }
 

chamaeleon

Member
Brief note:
- ds maps are slow
- dot operations are slow
try to keep out zone.zone for example outside the loop as a variable. If you absolutely need to use a dot operation and use it multiple times in the same iteration, make a shortcut variable.
Code:
var obj = tile.object;
if obj != ""
{
    if not ds_map_exists(asset_cache, obj)
    {
        ds_map_add(asset_cache, obj, asset_get_index(obj));
    }
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
}
Takes 200ms
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
    with (inst) {
        foo = "foo";
        bar = "bar";
        baz = "baz";
        quux = "quux";
    }
}
Takes ~1000ms
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
    inst.foo = "foo";
    inst.bar = "bar";
    inst.baz = "baz";
    inst.quux = "quux";
}
Takes ~1500ms

(Due to momentary lapse of reading comprehension of my own output, I have kept reading 0.2 as 20ms, when it is of course 200ms). I have never really created many instances for anything I've made so far, so I haven't experienced this pain point for myself. With this in mind I retract my suspicion that the time is spent outside this script unless evidence points to it being the case.
 
I finally realize the profiling graph's top level instance_create_level is not for the object you have replaced or experimented with its Create event. If Tree is the thing you swapped out for obj_foo in addition to not doing anything in the Create event, it looks like 5434 Tree instance_create_layer() calls only took 20ms, which means other code running as part of your "controller" instance Create event would be responsible.
Sorry, that was not the full profiler output. Here:

fullprofile.png

I appreciate the with() testing but I'm not doing anything to the objects I create that are causing this slowness.

Brief note:
- ds maps are slow
- dot operations are slow
try to keep out zone.zone for example outside the loop as a variable. If you absolutely need to use a dot operation and use it multiple times in the same iteration, make a shortcut variable.
Code:
var obj = tile.object;
if obj != ""
{
    if not ds_map_exists(asset_cache, obj)
    {
        ds_map_add(asset_cache, obj, asset_get_index(obj));
    }
Will keep in mind and already try to do that, but why are ds maps slow? Aren't they hash tables? Is it just the cache coherence?
 

Simon Gust

Member
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
}
Takes 200ms
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
    with (inst) {
        foo = "foo";
        bar = "bar";
        baz = "baz";
        quux = "quux";
    }
}
Takes ~1000ms
GML:
for (var i = 0; i < 100000; i++) {
    inst = instance_create_layer(300, 300, "Instances", obj_foo);
    inst.foo = "foo";
    inst.bar = "bar";
    inst.baz = "baz";
    inst.quux = "quux";
}
Takes ~1500ms
Kind of a predicament.
In this case I would insert the loop later in a single with() assuming the object is the same and evaluate the variables that way.
 

Simon Gust

Member
Will keep in mind and already try to do that, but why are ds maps slow? Aren't they hash tables? Is it just the cache coherence?
I mean, ds maps are slow compared to any other ds structure. You're searching for a spot in memory without order.
Maybe you can find an alternative.
You're searching for tile.object which is a string, does it need to be?
Can it be a number that is unique and insert it in a ds_queue or something?
This
Code:
ds_map_add(asset_cache, tile.object, asset_get_index(tile.object));
doesn't really mean much.
You're storing the value of a string via the string name. Essentially key and value are the same.
Not sure if that does all that much.
 
I mean, ds maps are slow compared to any other ds structure. You're searching for a spot in memory without order.
Maybe you can find an alternative.
You're searching for tile.object which is a string, does it need to be?
Can it be a number that is unique and insert it in a ds_queue or something?
This
Code:
ds_map_add(asset_cache, tile.object, asset_get_index(tile.object));
doesn't really mean much.
You're storing the value of a string via the string name. Essentially key and value are the same.
Not sure if that does all that much.
Huh? asset_get_index returns the ID of the object. tile.object is the name of the object, a string. Unless I'm mistaken I can't just give GM the name of an object and have it instantiate that.

Plus as the profiler shows the real slowdown is coming from instance_create_layer. Anything else is within acceptable tolerances. It's this line that is adding 2000ms to the load time:

GML:
instance_create_layer(xx*sw, yy*sh, obj_layer, obj_foo);
And I have no idea why.
 

chamaeleon

Member
This is a very desperate check, but just what, if anything, does ZoneTransition do in its Create event? Nothing at all? It is only 149 calls in your latest profile graph, but it does fall under the umbrella of the total 1900ms.
 
This is a very desperate check, but just what, if anything, does ZoneTransition do in its Create event? Nothing at all? It is only 149 calls in your latest profile graph, but it does fall under the umbrella of the total 1900ms.
ZoneTransition has no events. It is just an object that exists for collision and has some string/int variables which are set in the loop. Plus you can see those calls take up 0ms.

Now you understand my desperation. :p
 
Last edited:
What really confounds me is that it's only in SwitchZone. If I create 100,000 objects in CreateZone, called by the main menu button being created, then those instances are created just fine, in 200ms. So I thought, what if it's caused by there being an active connection? It couldn't possibly be the mere fact that I'm connected to a server, could it?

So I moved the player spawn code from the network event to the button click. Now it freezes for an entire minute while it's loading and I can't even profile it. Hooray! I hate this.
 
OK, somewhat ignominious end to this tale: I found out what the problem was by making a new project and retracting my steps. What you guys all missed is that I was loading an instance for each tile in the zone to get the data for it. That's 5625 ZoneTiles for each zone. There's 4 zones. So when I was creating new instances, there were already 22,500 on the map. Turns out when you have 22.5k instances already in a room, Game Maker really doesn't like to put 5k more in there. Plus it was totally unnecessary and wasteful for my purposes, so I moved the Tiles into two separate zone_objs and zone_tiles ds_grids instead. Now it loads in about 550ms instead of 2000ms, most of which is creating ds_lists. I'll work on it from there. Consider this issue resolved.
 
Top