How does AOT/JIT C# compilation work in Godot Mono?

Godot Version

3.4.2 (answers about Godot 4 welcome too)


For the uninitiated, C# assemblies include MSIL (Microsoft intermediate language code), AOT = ahead of time (compile all of the MSIL to native code before running the application), JIT = just in time (compile each function’s MSIL to native code before running that function for the first time)

I have a C# Godot project. When I export it, it uses JIT compilation, which causes problems, as there’s essentially a big lag spike the first time any piece of code runs. Particularly a problem when, say, an enemy uses an attack for the first time, and there’s a lag spike as the attacks’ code compiles.

Looking for ways to solve this.

I am aware of NGEN, which I think I might be able to run on the exported assembly during installation? But I’m worried that requires Mono to be installed on the target system. I assume I’d have to point it to the embedded version of Mono somehow…

I also see that Godot Documentation mentions AOT cross-compilers. If those are included with & run by the exported assembly at application startup that sounds great, but it’s not clear to me if that is what those AOT cross-compilers are for, or if they’re just for AOT compiling the engine itself.

This is not a c# issue there was a change in 4.1 or 4.0 that caused this.

Perhaps so, but I was encountering this in 3.4.2.

I did eventually fix my problem. I didn’t try NGEN, I did briefly try the AOT cross-compilers, but documentation on them is scant and building them is quite difficult, particularly since you need a custom mono build and, for Windows, you can’t build that natively on Windows and have to cross-compile for Windows from Linux.

My fix is the hackiest thing imaginable - just use reflection at application startup to loop over every class derived from Node and attempt to execute every single method in each of those classes inside of a try/catch block.

That’s obviously a terrible idea, but to make it a bit safer, I executed that reflection code inside of a separate AppDomain so it couldn’t affect the main process (as far as I’m aware).

Few weeks later and it seems to be working well. That hacky fix was much easier than building the AOT cross compilers or setting up NGEN, but it of course only works in .NET Framework.

1 Like

I don’t know how it works with Godot, but as far as I can tell all code is compiled at start, it may have to do some stuff when you load a new scene, but the documentation suggests using the preload over load if the scene resources have a static path. This solved the issue on my end.

After using preload, I’ve noticed if a parsing error is within a scene that is preloaded it will also throw a parsing error on the preload line. Suggesting that the scene is already cached from the preload line.