godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.63k stars 21.1k forks source link

Unexpected C# performance loss on simple scripts #89217

Open TheOrioli opened 7 months ago

TheOrioli commented 7 months ago

Tested versions

System information

Godot v4.3.dev4.mono - Windows 10.0.19045 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 2070 SUPER (NVIDIA; 31.0.15.4633) - AMD Ryzen 9 3900X 12-Core Processor (24 Threads)

Issue description

Short Version

C# performance seems to have suffer due to StringName equality checks in generated methods like HasGodotClassMethodand InvokeGodotClassMethod

Long Version

I noticed that performance in my game drops sharply with a large number of nodes doing basically nothing. So I created a small repro project to try and explain what I have observed.

In the repro project we have a very simple script that toggles a Label from a to b every second. This is done in a _process callback in both GDScript and C#. We instantiate 1000 instances of the label to simulate a large number of nodes. At a too large number (10000) the project is very slow, so differences between GDScript and C# are not noticable.

( all screenshots are in the repro project) Here are the graphs for the 4.x version of Godot: GDScript label_gd_4 3 C# label_mono_4 3

All 4.x versions tested exhibit the same behavior, which is GDScript being almost double the speed of C#.

For comparison here are the graphs for 3.5.3, where C# is faster than GDScript: GDScript label_gd_3 5 3 C# label_mono_3 5 3

I ran a dotTrace using Rider to try and figure out what is going on, and here are the results for 4.2.1 test_cache_4_2_1_trace

it's here I first noticed the equality checks on StringNames taking up a bit of time. Which led me to assume that the if-else chain in the HasGodotClassMethodand InvokeGodotClassMethod might be part of the problem.

I experimented in modifying the ScriptGenerators to use cached method calls instead of comparison checks https://github.com/TheOrioli/godot/commit/c291f64568899bb17d300655aa6a57179ae91cb0 and it seems to provide a bit of a speedup in my main project, but I do not know how to change those methods for built-in godot types.

You can see a small pyramid in the flame graph disappear under TestLabel.InvokeGodotClassMethod test_cache_custom_trace

I there is definitely something here, and there is potential to eliminate all those comparison checks which should provide a speedup and bring C# closer to GDScript in performance. I am hoping someone more familiar with the codebase can help out in figuring this out and testing more properly.

This behavior unfortunately means that making lots of small scripts in C# will lead to unintended performance degradation in games.

Steps to reproduce

Minimal reproduction project (MRP)

4.3-dev4, contains screenshots from above in folder test_mono_method_cache.zip 3.5.3 test_mono_method_gd3.zip

sky94520 commented 7 months ago

I am using Godot4.2 C#. I found FPS jitter rapidly, especially run code in Android(ualcomm - Adreno (TM) 630). What's strange is that in my scene, there are only around 10 nodes, and I've added a cache pool to avoid frequent node creation, but there is still an issue with FPS jitter. When I checked the jitter frames, I found that the Physics Frame went from less than 1ms to over 100ms.

image
Calinou commented 7 months ago

@sky94520 What you're reporting is likely an unrelated issue, as it encompasses an entire project and not a minimal reproduction case like OP did.

TheOrioli commented 7 months ago

I decided to remove the call to set the Text value, just to remove doubts that the problem lies in marshaling between C# calls to the Label property.

This is a graph of only 1 node being active, GDScript vs C#, and we can see there is basically no difference between the two, which makes sense Screenshot 2024-03-12 192100 Screenshot 2024-03-12 192011

However, increasing the number of nodes to just 10 creates a 14% difference GDScript gdscript_10 C# csharp_10

And, finally increasing to 100 nodes creates an almost 50% difference in favor of GDScript GDScript gdscript_100 C# csharp_100

Here is the updated project zip, but the basic difference is that the script truly does nothing except some basic math test_mono_method_cache.zip