Object Pooling In Godot: Worth It?

Поділитися
Вставка
  • Опубліковано 1 лют 2025

КОМЕНТАРІ • 63

  • @DeepDiveDevelop
    @DeepDiveDevelop  2 місяці тому

    To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/DeepDiveDev/ You’ll also get 20% off an annual premium subscription.

    • @Mika43344
      @Mika43344 Місяць тому

      Instead of using remove_at() you must pick last [i]from Array, guess it's pop_back() or smth..

  • @Jplaysterraria
    @Jplaysterraria Місяць тому +20

    Looking at 6:44 I see a potential perf problem in your implementation:
    If the object pool is an Array, then remove_at(0) is probably a performance pitfall; remove_at(0) will have to copy all elements that come after it so it's an O(N) operation, using the Array as a stack instead (removing the last element instead of the first) should always be more performant O(1). This is the case even if you have a Queue instead, Queues can pop the first and the last element in O(1) time, but this is more expensive than popping the last element of an Array.

    • @Grif_on96
      @Grif_on96 Місяць тому +3

      Yes .
      This is an example of how optimization techniques can lead to decreasing of the performance instead .
      So , profiler in the debugger is your best friend when optimizing !
      And of course genneral Computer Scince allows to quickly find such fuck-ups even without profiler :)

  • @Dark_Peace
    @Dark_Peace 2 місяці тому +34

    I'm the creator of BulletUpHell, the bullethell engine for Godot. I'm glad i implemented pooling even when everyone told me it was useless.
    I have an optimisation question, if anyone knows : someone told me to use custom classes instead of dictionaries to store data. In my mind, dict maje sense because they only contain a minimum of info, compared to ckasses who have always the same number of properties. The theory isn't very clear online but from what i read, godot dict are faster to get values from than classes. Except the person demonstrated the opposite to me.
    Idk. I was going to try, but if you guys know about that, please enlighten me.

    • @deme7272
      @deme7272 2 місяці тому +12

      The fastest way I know of storing and accessing large amount of data in godot from GDScript is through any of the packed arrays since they map more directly to a C++ array type the engine uses without as much of the overhead Variants include. This can lend Godot well to ECS style structures. So instead of having 100 bullet objects with 3 parameters you would have 3 arrays with 100 items each and in parallel (So params1[5] and params2[5] both are parameters on 'object' 5). This pattern generally works well with object pooling since even if you need a typed array of some resource (like a display texture) most of the game logic runs significantly faster. The other option to achieve best optimization results is to use custom C# or C++ code. This one is similar to the first as you can avoid the overhead of Variants and take advantage of C++ or C# language optimization features.
      Past that godot does do some early pseudo compiling of gdscript before and on run which is why when you have to hot reload it can take a bit and doesn't always work. This leads to weird tricks like " var arr: PackedInt64Array = PackedInt64Array() " is slower than " var arr := PackedInt64Array() ". The one that is relevant in this case is the "." (dot) operator. Doing object.get("param name") is about the same as dict["param"] not exactly but gdscript leaves the string as is so it can be slower at runtime. Doing object.param is faster because godot is able to find what you want before runtime instead of evaluating it at runtime. I'm not certain but this may mean doing dict.param gains some speed ups too. Its a fairly new way of accessing dicts so I'm not certain but based on my understanding of the engine it could.

    • @Dark_Peace
      @Dark_Peace 2 місяці тому +6

      @deme7272 thank you. I wouldn't have thought about using arrays that way.

    • @skyjoe55
      @skyjoe55 2 місяці тому +5

      ​@@deme7272 can you explain what makes the ": class =" slower than ":="? (Is it just slower at compile time because theres an extra check or is it also slower when running? Does it specifically have to do with Packed Arrays specifically?)

    • @Sylfa
      @Sylfa Місяць тому +2

      Please use your array as a circular buffer. What was done in this video where they grab the first item from the array and then remove it is not the optimal way to deal with thousands of items, it requires the items to shift position in the array. That is, the array[0] = array[1] all the way up to the end of the array. Though the array implementation is faster than doing it like that it still needs to copy the data around.
      To make a circular buffer you have an array and two integer values. The first one is the position you will put the next item into the buffer at. The second is where you pull the next item out from.
      So when you put in an item you do:
      buffer[write_pos] = value
      write_pos = (write_pos + 1) % capacity
      And to read you do:
      var value = buffer[read_pos]
      read_pos = (read_pos + 1) % capacity
      If read_pos == write_pos it's empty. And if (write_pos + 1) % capacity == read_pos then it's full.
      There's a few caveats, like resizing the buffer first to ensure it doesn't resize during game play and handling the situation where you run out of space in the buffer. At that point you either just queue_free the new item or you do a slower operation where you create a new buffer that is larger and copy the old items over.

    • @Sylfa
      @Sylfa Місяць тому +2

      @@skyjoe55 It's slower because GDScript isn't compiled when you build your game, it's interpreted so it needs to do it when the game is running. That said, it *should* only do it once because they convert it into an intermediary format, but I haven't made sure that's the case. As always, measure before you optimize. Premature optimization makes the game harder to write and you might waste days working on something that doesn't change the performance.

  • @ivory_lion
    @ivory_lion 2 місяці тому +3

    Thanks for covering the questions I posited last vid.
    Been waiting for this topic eagerly, thanks again!

  • @dealloc
    @dealloc Місяць тому +12

    Keep in mind that measuring performance is not enough. Understand _why_ it is slow before making optimizations, like pooling. Vast majority of cases are because instantiation of objects take too long (like slow calculations during initialization, or too much allocation upfront when you don't need it). Limit what your objects needs to do when they are instantiated.
    Chances are you can get away with very minor changes that doesn't need to involve data structures like ring buffers, linked lists or some other arbitrary structure.
    See where you could pre-compute values, possibly store them as static values, and where your initializations could be deferred or lazily evaluated. It's often slower to tame memory by adding more abstractions, than for example just recalculating things
    If you know you're being limited by other factors, like garbage collector, before just picking the first tool that some comment tells you, try different ways (easiest first), and measure.

  • @Codethe_Road
    @Codethe_Road Місяць тому +3

    I was running into performance issues in my godot project, and I implemented some of my own "scratch" fixes--- but this technique did help quite a bit when I did get around to trying it.

  • @Kry0000
    @Kry0000 2 місяці тому +1

    This video has helped clarify and demystify my understanding of object pooling, thank you.

  • @RebelliousX
    @RebelliousX 2 місяці тому +11

    I don't know why you are removing the first item instead of pop() the last item! If Array in Godot is implemented similarly to Vector in C++ then that could lead to huge loss of performance. According to Juan, Arrays in Godot are a modified version of std::vector of C++. And Vectors in C++ aren't designed for efficient front element removal. I would treat the object pool array as a stack, Last In First Out ( or LIFO ). Always, push_back() to add, and pop() to remove and retrieve.

  • @pietraderdetective8953
    @pietraderdetective8953 2 місяці тому +1

    Hey what a great content!
    We need more of these Godot optimization techniques. More of these please..
    Liked and subbed!

  • @kerduslegend2644
    @kerduslegend2644 Місяць тому +4

    Remember, instantiating more object is a lot faster than getting an already instantiated object

  • @SpotAllen
    @SpotAllen Місяць тому +1

    I like your style man. Subbed.

  • @Nora_Short
    @Nora_Short Місяць тому

    thank you for this video, i'm new to Godot so this video was very useful!

  • @1000_Gibibit
    @1000_Gibibit Місяць тому +2

    Why instantiate lot object when pool few object do trick?
    Glad you covered this, pooling can be very helpful. One thing you didn't explicitly mention, I think, is that pooling can move some initialization costs from hitting during runtime to a loading screen. So even if you only expect 1,000 instances of something in a level, spawning them all up front can give a performance boost. This can be helpful on mobile platforms where you don't get a lot of CPU time but you will probably have some leftover RAM. Not just in theory, this helped me and my team in a production environment once.

  • @wicked_sash
    @wicked_sash 2 місяці тому +10

    if gdscript lets you, make your array a fixed size. constant memory allocation is quite slow at such a big level.

    • @Cammymoop
      @Cammymoop Місяць тому +1

      Presizing the array will be faster of course, but one thing to keep in mind is that dynamically sized arrays are meant to be added to and are optimized for it, specifically when they need to grow they do it proportionally to how big they already are. Meaning that adding objects is "amortized" O(log n) time complexity rather than O(n), if adding to the array isn't done constantly or very often you probably should look elsewhere for speed :)

  • @synccyt_
    @synccyt_ 2 місяці тому +3

    this is a great video, explained it perfectly. the added graphics really sell it

  • @juanhernandez-up4pg
    @juanhernandez-up4pg Місяць тому +1

    Great video, thanks 👍

  • @xyprohero
    @xyprohero 2 місяці тому +1

    thanks im new to godot always looking for game optimize... Thanks alot for this!!!

  • @MaceTLOF
    @MaceTLOF 2 місяці тому +2

    I did this automatically because when i originally tried the spawn and remove the player's attacks it would lag my game out each time the abilities were used, pooling worked a lot better for me

  • @mohsenzare2511
    @mohsenzare2511 Місяць тому +1

    good video! I also think pool can increase performance! But I think the GDScript is not a best option to create this! in Array you keep a pointer to the object, so object are not next to each other in memory which will not get the best performance! another think adding and removing object to array is performance costly sometimes for this you need to allocate memory which is the same thing as allocating memory for object! even if you want to remove from array it is better to remove the last element! by the way it is best this should be implemented in core or GDExtension

  • @ai_buddhist_art
    @ai_buddhist_art 2 місяці тому +6

    Thanks for the video and test results. However, I curious about remove_at(0) vs pop_back() performance comparison. Could you please compare it? Thanks😊

    • @DeepDiveDevelop
      @DeepDiveDevelop  2 місяці тому +6

      I checked the docs and apparently pop_back() is faster than remove_at(0) or pop_front(), so I'd use that, especially with larger pool sizes. Nice catch! Looks like its best to prefer pop_back() and push_back() over pop_front() and push_front() to prevent having the shift all the array indices. Sometimes you need pop_front() though, like for BFS algorithms

    • @ai_buddhist_art
      @ai_buddhist_art 2 місяці тому +1

      @ Thanks for reply. That’s right. The underlying array implementation has to copy a huge data when push/pop in front of the array. So I think push/pop back would improve performance for your test. Would be nice if you can make a video comparison.

    • @DeepDiveDevelop
      @DeepDiveDevelop  2 місяці тому +1

      I can add that to the list!

  • @duke605
    @duke605 Місяць тому

    Idk how godot does it's arrays but it might be better to also store a cap and and length variable and when pulling, just reduce the length and when adding, add to array using the length index unless the array is at its cap. The just use push and increase cap by 1. This will prevent a bunch of array copies and allocations

  • @IogaMaster
    @IogaMaster 2 місяці тому

    Not using Godot, but writing my own engine ontop of raylib in odin.
    This sounds like a great idea for the ecs, to reuse components!

  • @queblegamedevelopment4143
    @queblegamedevelopment4143 2 місяці тому +1

    Great video!

  • @Sucundrule
    @Sucundrule 2 місяці тому +1

    amazing video, thanks

  • @chaosmachines934
    @chaosmachines934 2 місяці тому +1

    i will so going to use it on almost everything and see how it will preform

  • @Cammymoop
    @Cammymoop Місяць тому +3

    Nice video, though as others pointed out you most likely want to take objects out of the end of the pool rather than the start. I do want to "devil's advocate a little though, what Juan said was a little misleading I've seen people quote that tweet a bunch in the same context, so that's fair. But the example you showed really isn't what he was trying to address, I think he's responding to people wanting every single game object to be pooled in one or more pools regardless of how often they are added or removed from the game environment. The way he said it in that tweet can be interpreted any number of ways though, but imo it's not even presented as advice on optimization, just a random thought from Juan 6 years ago.
    For a more constructive critique, "Simple area2ds with sprites" are not that simple and not the best or simplest way to implement a large amount of bullets like that. That's 2 whole nodes in the tree and lots of functionality you will never need for those bullets. They are the easiest way to do it, yes, so if a simple pool of area/sprite bullets is good enough performance then go for it! The Godot docs specifically point out how the scene tree isn't always the best approach and show how to use the Server classes like the PhysicsServer and RenderingServer to manage actual lightweight instances (that live outside of GDScript comletely, other than a reference RID). There's also 2 different types of particle systems, which aren't as easy to use in collisions (you'll have to set up collision detecting manually) but are definitely an option and they handle the pooling for you plus have lots of nice options out of the box that could be useful for different kinds of firing patterns or bullet behaviors. Creating and destroying 3000+ objects a second is still always going to have a performance impact and if you need to go even further beyond that it will only get worse and worse, so pooling is the obvious approach for things like bullets in a shmup/bullet hell.

    • @DeepDiveDevelop
      @DeepDiveDevelop  Місяць тому +1

      Thanks for the feedback. To be clear, the video might've made it seem this way, but I wasn't trying to bring down Juan or stir the pot in any way with that tweet, he knows the engine 100x better than me. And your definitely right there are further optimizations that could be done to probably get even more bullets per second and fps with the quick example I show. I do plan on touching on using the server classes in the future. Might even do a follow up video on pooling if there's interest. Thanks for watching!

    • @Cammymoop
      @Cammymoop Місяць тому

      @@DeepDiveDevelop Don't worry, it didn't come across like you were pot stirring haha. I just figured the dude has enough on his plate already, with godot becoming such a big thing in the last few years, and the whole, uh PR incident thing. So I wanted to provide a mediating voice for anyone who might be the conclusion jumping type

  • @MattGiuca
    @MattGiuca 2 місяці тому +1

    I normally remove objects from the tree (but dont free them) in situations like enemies dying that I can respawn later. Now you've got me thinking what might be the pros and cons (both performance and other potential issues) of doing this (hiding and disabling process) versus removing from the tree.
    Like, if you just hide and disable process, won't collisions still occur with the "dead" objects? I have not tried it ...

    • @DeepDiveDevelop
      @DeepDiveDevelop  2 місяці тому +3

      I experimented with this a bit and it seems like keeping the pooled objects in the tree is the most performant since add_child can take a while. The only way I got 'dead' objects in the tree to stop colliding was to set their collision mask and layer to 0.

    • @MattGiuca
      @MattGiuca 2 місяці тому

      @DeepDiveDevelop Ah great to know. Thanks!

    • @Cammymoop
      @Cammymoop Місяць тому +2

      setting process to disabled on the collision body should remove it from the physics system while it's disabled, as long as it's disable_mode is DISABLE_MODE_REMOVE which is the default

  • @ArcadeNestGames
    @ArcadeNestGames 2 місяці тому +3

    More complex the object is more profit is to switch to pooling.

  • @xornedge8204
    @xornedge8204 Місяць тому

    This is the best explanation I've ever seen. It just makes sense!

  • @ajpink5880
    @ajpink5880 2 місяці тому +2

    I wonder how we could best re-educate Juan... But people in the Godot community do this a lot, where they throw out theoreticals but don't actually try it before they start talking. If you don't know if something will gain/lose performance, just try it and see. Theoreticals can only get you so far.

  • @noobkiller100
    @noobkiller100 Місяць тому

    very interesting subscribed

  • @StrangerInAStrangeLand1999
    @StrangerInAStrangeLand1999 2 місяці тому +23

    It's almost like Juan isn't the most honest person...

    • @abhijitleihaorambam3763
      @abhijitleihaorambam3763 2 місяці тому +6

      he also said rust doesn't belong to godot. Now he is trying to tell you can use Rust in godot.

    • @toolazytobeoriginal4587
      @toolazytobeoriginal4587 2 місяці тому

      ​@@abhijitleihaorambam3763 are you sure that he didn't mean that rust doesn't belong integrated into Godot (like GDScript)? As far as I know the use of rust in Godot is via GDExtension

    • @ultimaxkom8728
      @ultimaxkom8728 2 місяці тому +7

      _`I am apolitical`_
      Proceed to write a political tweet on Mastodon about _"haters"_ weebs and Trump anyway. Very apolitical.

    • @NihongoWakannai
      @NihongoWakannai 2 місяці тому +4

      He made a game engine based around a dynamic, interpreted language. I don't think he's too concerned about heavy optimisation.

    • @drinkwwwaterrr
      @drinkwwwaterrr 2 місяці тому +6

      I think he's just unaware and bases his claims of theory rather than actual use cases since he writes the engine but doesn't actually make games with it.
      A lot of issues with Godot actually stem from this, if the devs did the same as Epic and stress tested their engine in a real, large scale project there would be so much stuff that would already be fixed as otherwise it wouldn't be possible to actually properky make the game.

  • @pixiri21
    @pixiri21 Місяць тому

    Best advice I can give for Godot devs, never listen to the people who make Godot. :)