Anyone have a good Instance pooling demo?

csanyk

Member
Looking for a well-coded demo showing how to do instance pooling in GMS.

For the uninitiated, instance pools are a design pattern that is useful for performance optimization when you are constantly creating/destroying lots of intstances. Rather than creating and destroying instances every step of the game, you create a "pool" of (deactivated) instances, as many as you would need for the game, ahead of time, which you manage by activating/deactivating as needed. When you'd ordinarily destroy the instance, you simply deactivate it, to re-use it when you need another one.

As long as activation/deactivating is cheaper than creation/destruction in terms of CPU, it is beneficial to performance.
 

Mike

nobody important
GMC Elder
Just deactivate, and then use the instance ID as the "handle" to allocate/deallocate


https://books.google.co.uk/books?id=iYALAAAAQBAJ&pg=PA393&lpg=PA393&dq=Stack+Allocation+game+programming+gems&source=bl&ots=ZZQX1ZggO0&sig=6k4y1JxNYcmcfmFMelIXv8YAlsY&hl=en&sa=X&ved=0ahUKEwjT04fi9fPQAhVHIsAKHYSeBxcQ6AEIHzAB#v=onepage&q=Stack Allocation game programming gems&f=false

Basically, create a ds_stack, then anything you free you deactivate and push on the stack.

When allocating, if the stack isn't empty, pop off the ID. If it IS empty, do an instance_create() on it. When it gets free'd it'll go back into the stack list. You can even decide that if you go over a certain number of "free" items, to delete it. So you only ever keep a max stack of "N" free ones.

This is the fastest allocation system I've come up with.
 

csanyk

Member
Just deactivate, and then use the instance ID as the "handle" to allocate/deallocate


https://books.google.co.uk/books?id=iYALAAAAQBAJ&pg=PA393&lpg=PA393&dq=Stack+Allocation+game+programming+gems&source=bl&ots=ZZQX1ZggO0&sig=6k4y1JxNYcmcfmFMelIXv8YAlsY&hl=en&sa=X&ved=0ahUKEwjT04fi9fPQAhVHIsAKHYSeBxcQ6AEIHzAB#v=onepage&q=Stack Allocation game programming gems&f=false

Basically, create a ds_stack, then anything you free you deactivate and push on the stack.

When allocating, if the stack isn't empty, pop off the ID. If it IS empty, do an instance_create() on it. When it gets free'd it'll go back into the stack list. You can even decide that if you go over a certain number of "free" items, to delete it. So you only ever keep a max stack of "N" free ones.

This is the fastest allocation system I've come up with.
Thanks for the suggestion to use a ds_stack.... I had been working on a demo implementation using an array to track the pool, and was finding that the overhead to manage the array and track which instances were active was nullifying any advantage I gained by pooling vs. newing. As soon as you suggested a stack, I realized it was a lot more self-managing due to the nature of the data structure. I'll have to try this approach and see how much of a difference it makes.
 

obscene

Member
I've been considering how to do this too so glad you asked and Mike answered. :D

I'm trying to do it with sparks that emit from lasers and bounce on the floors and fade away, so each time they are activated I also need to reset their speed, direction, size, color, alpha, etc. When changing rooms I'm assuming deactivated sparks would be destroyed and I'll need to recreate them again right quick and deactivate them to have them ready for use?
 

Mike

nobody important
GMC Elder
pretty sure deactivated persistent ones are too.... But you'd have to check.
 
Total noob to Game Maker here, but in GameSalad they solved it by creating the needed objects off-screen and just moving them in and out of view as needed. Not sure if this is possible in GMS or if it's a good solution.
 

FrostyCat

Member
Total noob to Game Maker here, but in GameSalad they solved it by creating the needed objects off-screen and just moving them in and out of view as needed. Not sure if this is possible in GMS or if it's a good solution.
You can sweep these instances under the rug the same way in GM, but the problem is that they would still take up CPU cycles by processing its Step and Draw events, being counted in collision checking, etc. The point of deactivating the unused instances is to stop all that until they're needed again.
 

obscene

Member
I just finished my sparks scripts, first time trying pooling, it was easy and efficient. :) Only thing I did different from what Mike suggested was I make a pool of 200 of them on Room Start (after clearing the stack of cours) so I never have to create them on the fly. When I normally destroyed the sparks I now execute a user event that pushes their IDs onto the stack and deactivates them.
 
You can sweep these instances under the rug the same way in GM, but the problem is that they would still take up CPU cycles by processing its Step and Draw events, being counted in collision checking, etc. The point of deactivating the unused instances is to stop all that until they're needed again.
Ah ok, so deactivating them is even more efficient then, good to know.
 

csanyk

Member
I've written an article on my experimentation with instance pooling:

https://csanyk.com/2016/12/gms-instance-pooling-performance-optimization/

Curiously, I did not find that it offered a great boost in performance, at least not on my system, unless the Create/Destroy events were heavy.

Using the profiler on my demo project, which you can download from the above article, I found that when my instances are simple and light weight, calling Create/Destroy incurs about the same amount of overhead as does managing the stack, activating/deactivating, and resetting the state of the instances for re-use.

This only appears not to be the case if the instance has a lot of work to do at Create (or Destroy, I suppose), which does not need to be repeated if the instance is deactivated and reused.

Pretty interesting findings. I'd love to know whether this changes on different build targets. If anyone would like to download my demo projects and test them on Mac OS X, Android, iOS, etc., I'd love to hear your findings.
 

icuurd12b42

TMC Founder
GMC Elder
Because you are deactivating instances possibly. also I think I saw russel say they improved the speed of instance create/destroy somewhere


 

Juju

Member
There's a nice implicit benefit of instance pooling: It makes handling multiplayer netcode for complicated scenes easier if you can refer to instances by a common index across all machines.
 

csanyk

Member
There's a nice implicit benefit of instance pooling: It makes handling multiplayer netcode for complicated scenes easier if you can refer to instances by a common index across all machines.
I hadn't even contemplated that... but that's a good point.

From running the demo project in the code profiler, I was able to see that the GML code I wrote only uses about 10% of the overall CPU time, while the Engine takes about 90%.

So, it turns out that the Create-Destroy approach and the Instance Pool approach were using about the same amount of CPU, just with different approaches. In the instance pool, Activating/Deacting was only part of the overhead -- there was also popping/pushing id's to the ds_stack, as well as re-setting some variables in the instances when they were re-used. It turns out that the sum of all of the above is about equal to the overhead taken up by Create-Destroying bullet instances.

If I didn't have to reset the state of a re-cycled instance, or if doing so could be done much more cheaply than creating a new instance, it would be advantageous to use pooling.

I'll probably update my article on the blog later with some figures that I gleaned from the profiler.
 

Mike

nobody important
GMC Elder
I'd also consider that memory allocation is slow - done by the engine, and that constant freeing and reallocating will fragment memory, and cause more cache misses overall. Pooling keeps things much more local, and it'll stay in the cache.
 

csanyk

Member
I'd also consider that memory allocation is slow - done by the engine, and that constant freeing and reallocating will fragment memory, and cause more cache misses overall. Pooling keeps things much more local, and it'll stay in the cache.
Appreciate that input, I think I speculated to this effect in my article, but now I can update it with confirmation :) Thank you!
 
Last edited:
Top