Godot: Removing and adding lots of sprites is very slow if done in the same tick

Created on 17 Feb 2020  路  3Comments  路  Source: godotengine/godot

Godot version: 3.2 stable

OS/device including version: Arch Linux, kernel 5.5.2

Issue description:
When deleting and adding a lot of sprites at once Godot's performance heavily depends upon whether deletion and addition are done in the same tick or not.

In detail: I added around 3000 sprites as children to the same (empty) parent by a script which is reasonable fast. When I then sometime later remove all those nodes again (by calling queue_free() on them and removing them from the parent) and immediately add another 3000 new ones to the same parent, Godot takes significantly more time than when just creating and adding new sprites. I'd like to note that the hiccup occurs between frames (i.e. Godot's calls to _process()) and not during my calls to queue_free() or add_child() or anywhere in my script code.

I was then able to find a kind of workaround that is a lot faster. Instead of deleting and adding all the sprites in the same tick, I first delete all sprites in one _process() call and delay creation of new ones until the next _process() call

The slow down factor depends - according to my experiments - on the number of nodes removed/added. When deleting and adding 10000 sprites it takes about 1 second when splitting work into to ticks while it takes about 10 seconds when doing all the work in one tick, while the factor is less than 2 when using just 500 nodes.

It is also important to node that the difference is measurable but rather low when removing and adding bare Node2Ds instead of sprites. I got a difference of 200 ms of about 12 % when trying 40000 nodes.

The observations I made where basically the same when running the project in the editor or a Linux or HTML5 export.

Steps to reproduce:
Download the example project I created and run it. You should see debug/console output in the likes of

Preparing ...
Removing nodes
Creating nodes
Action took 885 ms
Removing and recreating nodes
Action took 10642 ms
Done

You can then close the window. To run the experiment with different number of sprites change num_sprites of the root node in scene Main.tscn or in the script Main.gd.

Minimal reproduction project:
Here I'm using OS.get_ticks_msec() to measure time. The first number printed is the time measured over two ticks, first deleting previously created nodes, second adding the same amount again. The second number printed is measured over one tick where deletion and addition are done immediately after another.

*The starting time stamp is stored in the first tick where the work load starts. The ending time stamp is retrieved in the tick immediately following the tick where the work load ends. This allows the time between calls to _process() to be included in the measurement.

BugQueueFreeDelay.zip

bug core

Most helpful comment

It would be interesting to test whether this also happens when using C#聽or C++ (via GDNative or a module).

Considering I have translated the gdscript code correctly to C (I've done it the first time now) the behaviour is the same. Also interesting that the actual numbers are virtually identical between gdscript and native code. The C code is usually a tad faster, though at 10000 sprites the gdscript code reproducibly wins by a tiny bit when doing everything in the same tick. The advantage is about 300 ms of about 11 seconds total. At 20000 it was for example 45752 ms (native) vs. 46091 ms.

[Edit]
Just for the record: using Node2Ds instead of sprites yields native code being about 300 ms faster than gdscript in both cases with a total time between 3 and 4 seconds at 40000 nodes.

All 3 comments

It would be interesting to test whether this also happens when using C#聽or C++ (via GDNative or a module).

I also found a while ago that adding and removing nodes during the same frame has an increasing cost the more nodes there are, and in fact, in this test I'm only using one node: https://github.com/Zylann/gdscript_performance/blob/master/project32/tests/test_add_remove_node.gd

It would be interesting to test whether this also happens when using C#聽or C++ (via GDNative or a module).

Considering I have translated the gdscript code correctly to C (I've done it the first time now) the behaviour is the same. Also interesting that the actual numbers are virtually identical between gdscript and native code. The C code is usually a tad faster, though at 10000 sprites the gdscript code reproducibly wins by a tiny bit when doing everything in the same tick. The advantage is about 300 ms of about 11 seconds total. At 20000 it was for example 45752 ms (native) vs. 46091 ms.

[Edit]
Just for the record: using Node2Ds instead of sprites yields native code being about 300 ms faster than gdscript in both cases with a total time between 3 and 4 seconds at 40000 nodes.

Was this page helpful?
0 / 5 - 0 ratings