godotengine / godot

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

C# running in Godot is about 5x slower than running it from a Console app #96435

Open dotlogix opened 1 month ago

dotlogix commented 1 month ago

Tested versions

System information

Windows 11, Godot 4.4-dev, Vulkan, Jetbrains Rider, .Net 9

Issue description

In one of my tests I tried to run some benchmarks because I couldn't find any issues in my code anymore related to performance and profiling did not lead to any significant results.

In that test I executed the exact same logic from a Console app to use it with Benchmark.Net and noticed that the code running in the Console is about 5x as fast than running it in Godot.

In my actual environment these calls are running in a separate Thread pool so it shouldn't be related to Godot itself but to the .Net runtime used by Godot

Expected: Performance of C# code running in Godot is about the same as running it from a Console app

Actual: Performance of C# code running in Godot is ~5x slower than running it from a Console app

Steps to reproduce

Create a C# project and create a CPU heavy task. Run this in a tight loop ~10k times

Execute the same logic in a Console app and compare the results

Minimal reproduction project (MRP)

performancetest.zip

This is a small part of my VoxelEngine I am currently writing in pure C#, it contains quite a bit of Code but for very simple tasks I couldn't produce a huge amount of difference ~1ms per iteration.

Pls note that this code runs before anything renders at all so I would assume it should perform the same. But this project shows a HUGE difference. The results are: Console: Took 23s Godot: Took 1m 30s

takethebait commented 1 month ago

I just gave this a spin and my first thought was that this looked like the difference between Debug and Release Configurations.

When I run the standalone console application in Debug I get ~1:22 min of execution time. When I run it as Release it's sitting at ~22s.

However, even when exporting the project in Godot, it always stays at 1:22 min even when manually ticking "Optimize Code" in the csproj settings. Project was exported using the release export template.

While tracing I noticed a massive difference in call counts for TerrainVoxelGeometryLayer.Add

Console Release:

console

Godot (Edit: updated the screenshot as in the old one the trace did not run to completion): godot_2

I get a similar disproportionate high call count when running the standalone console as debug: debug

My uneducated guess is that something is messing with properly exporting the project as Release.

Edit: I'm also running this using .NET 8 as Godot just does not want to load my .NET 9 assemblies.

raulsntos commented 1 month ago

We don't use the Release configuration, in the Godot.NET.Sdk we define 3 configurations: Debug, ExportDebug, and ExportRelease:

https://github.com/godotengine/godot/blob/61598c5c88d95b96811d386cb20d714c35f4c6d7/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props#L12

Like the default Release configuration, we set Optimize to true when using the ExportRelease configuration:

https://github.com/godotengine/godot/blob/61598c5c88d95b96811d386cb20d714c35f4c6d7/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props#L46-L56

Can you check the binlogs to see if Optimize was properly set to true? Also, can you check explicitly setting it to true in the Common.csproj project? I suspect it may not be setting it because that project uses the Microsoft.NET.Sdk which won't recognize the ExportRelease configuration.

dotlogix commented 1 month ago

We don't use the Release configuration, in the Godot.NET.Sdk we define 3 configurations: Debug, ExportDebug, and ExportRelease:

https://github.com/godotengine/godot/blob/61598c5c88d95b96811d386cb20d714c35f4c6d7/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props#L12

Like the default Release configuration, we set Optimize to true when using the ExportRelease configuration:

https://github.com/godotengine/godot/blob/61598c5c88d95b96811d386cb20d714c35f4c6d7/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props#L46-L56

Can you check the binlogs to see if Optimize was properly set to true? Also, can you check explicitly setting it to true in the Common.csproj project? I suspect it may not be setting it because that project uses the Microsoft.NET.Sdk which won't recognize the ExportRelease configuration.

That indeed fixed the issue. So I assume the whole solution is built with ExportRelease which then does not apply optimize true when building normal assemblies.

Good to know. Maybe sth which should be mentioned in the docs? I have to test what the minimal working setup is for this

dotlogix commented 1 month ago

image That indeed does the trick. Down from ~20-30ms to just 2.5ms awesome <3 thank you so much

dotlogix commented 1 month ago

For other ppl just add a Directory.Build.props with this content:

<Project>
    <PropertyGroup>
        <Configurations>Debug;ExportDebug;ExportRelease;</Configurations>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
        <VersionSuffix>preview-0001</VersionSuffix>
        <DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
        <Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
        <Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
    </PropertyGroup>
</Project>
raulsntos commented 1 month ago

This seems like something that could be better handled with support from upstream (see https://github.com/dotnet/sdk/issues/31918). Then we could let .NET know that ExportRelease is meant to be a "Release" configuration, and the SDK could handle it accordingly instead of checking the configuration name like it does now.

However, we should also consider whether we actually need 3 configurations or if we can just use the default Debug and Release configurations. It looks like we currently check the configuration name to only add the TOOLS constant to the non-export Debug configuration, and avoid adding it to exported games:

https://github.com/godotengine/godot/blob/61598c5c88d95b96811d386cb20d714c35f4c6d7/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props#L109-L110

But maybe there's another way to handle this. For example, a property like _IsPublishing, although I'm not sure how I feel about using an undocumented and clearly private property (since the name starts with an underscore).

I think this is something that should gracefully work, a lot of users would probably find this behavior surprising and currently we don't document the custom Godot configurations anywhere. Let's keep this issue open until we figure something out.

thygrrr commented 1 month ago

Can we run our .NET code (or some of their assemblies) in release configuration while in the editor? That would be very useful.

Either way, what speaks against supporting proper "Release" for release exports? :D

raulsntos commented 1 month ago

Can we run our .NET code (or some of their assemblies) in release configuration while in the editor? That would be very useful.

Currently no. It's hardcoded to use the Debug configuration.

what speaks against supporting proper "Release" for release exports?

Not sure, I wasn't around when the 3 configurations were introduced so I'm not familiar with the history. It's possible there were really good reasons to introduce them, but maybe things changed since then.

And we'd also want to make sure it doesn't break existing projects, so we may need some migration code if we change the configurations.