In general when game development comes up here I tend not to engage as professional gamedev is so different than what other people tend to deal with that it's hard to even get on the same page, but seeing as how this one is very directly dealing with my expertise I'll chime in.
There are few things off with the this post that essentially sound as someone more green when it comes to Unity development (no problem, we all start somewhere).
1. The stated approach of separating the simulation and presentation layers isn't all that uncommon, in fact it was the primary way of achieving performance in the past (though, you usually used C++, not C#).
2. Most games don't ship on the mono backend, but instead on il2cpp (it's hard to gauge how feasible that'd be from this post as it lacks details).
3. In modern Unity, if you want to achieve performance, you'd be better off taking the approach of utilizing the burst compiler and HPC#, especially with what appears to be happening in the in sample here as the job system will help tremendously.
4. Profiling the editor is always a fools errand, it's so much slower than even a debug build for obvious reasons.
Long story short, Unity devs are excited for the mentioned update, but it's for accessing modern language features, not particularly for any performance gains. Also, I've seen a lot of mention around GC through this comment section, and professional Unity projects tend to go out of their way to minimize these at runtime, or even sidestep entirely with unmanaged memory and DOTS.
Author here, thanks for your perspective. Here some thoughts:
> approach of separating the simulation and presentation layers isn't all that uncommon
I agree that some level of separation is is not that uncommon, but games usually depend on things from their respective engine, especially on things like datatypes (e.g. Vector3) or math libraries. The reason I mention that our game is unique in this way is that its non-rendering code does not depend on any Unity types or DLLs. And I think that is quite uncommon, especially for a game made in Unity.
> Most games don't ship on the mono backend, but instead on il2cpp
I think this really depends. If we take absolute numbers, roughly 20% of Unity games on Steam use IL2CPP [1]. Of course many simple games won't be using it so the sample is skewed is we want to measure "how many players play games with IL2CPP tech". But there are still many and higher perf of managed code would certainly have an impact.
We don't use IL2CPP because we use many features that are not compatible with it. For example DLC and mods loading at runtime via DLLs, reflection for custom serialization, things like [FieldOffset] for efficient struct packing and for GPU communication, etc.
Also, having managed code makes the game "hackabe". Some modders use IL injection to be able to hook to places where our APIs don't allow. This is good and bad, but so far this allowed modders to progress faster than we expected so it's a net positive.
> In modern Unity, if you want to achieve performance, you'd be better off taking the approach of utilizing the burst compiler and HPC#
Yeah, and I really wish we would not need to do that. Burst and HPC# are messy and add a lot of unnecessary complexity and artificial limitations.
The thing is, if Mono and .NET were both equally "slow", then sure, let's do some HPC# tricks to get high performance, but it is not! Modern .NET is fast, but Unity devs cannot take advantage of it, which is frustrating.
By the way, the final trace with parallel workers was just C#'s workers threads and thread pool.
> Profiling the editor is always a fools errand
Maybe, but we (devs) spend 99% of our time in the editor. And perf gains from editor usually translate to the Release build with very similar percentage gains (I know this is generally not true, but in my experience it is). We have done many significant optimizations before and measurements from the editor were always useful indicator.
What is not very useful is Unity's profiler, especially with "deep profile" enabled. It adds constant cost per method, highly exaggerating cost of small methods. So we have our own tracing system that does not do this.
> I've seen a lot of mention around GC through this comment section, and professional Unity projects tend to go out of their way to minimize these at runtime
Yes, minimizing allocations is key, but there are many cases where they are hard to avoid. Things like strings processing for UI generates a lot of garbage every frame. And there are APIs that simply don't have an allocation-free options. CoreCLR would allow to further cut down on allocations and have better APIs available.
Just the fact that the current GC is non-moving means that the memory consumption goes up over time due to fragmentation. We have had numerous reports of "memory" leaks where players report that after periodic load/quit-to-menu loops, memory consumption goes up over time.
Even if we got fast CoreCLR C# code execution, these issues would prevail, so improved CG would be the next on the list.
>We don't use IL2CPP because we use many features that are not compatible with it. For example DLC and mods loading at runtime via DLLs, reflection for custom serialization, things like [FieldOffset] for efficient struct packing and for GPU communication, etc.
FieldOffset is supported by IL2CPP at compile time [0]. You can also install new DLLs and force the player to restart if you want downloadable mod support.
It's true that you can't do reflection for serialization, but there are better, more performant alternatives for that use case, in my experience.
> You can also install new DLLs and force the player to restart if you want downloadable mod support.
I am not aware of an easy way to load (managed) mods as DLLs to IL2CPP-compiled game. I am thinking about `Assembly.LoadFrom("Mod.dll")`.
Can you elaborate how this is done?
> there are better, more performant alternatives for that use case, in my experience.
We actually use reflection to emit optimal code for generic serializers that avoid boxing and increase performance.
There may be alternatives, we explored things like FlatBuffers and their variants, but nothing came close to our system in terms of ease of use, versioning support, and performance.
If you have some suggestions, I'd be interested to see what options are out there for C#.
> FieldOffset is supported by IL2CPP at compile time
You are right, I miss-remembered this one, you cannot get it via reflection, but it works.
>I am not aware of an easy way to load (managed) mods as DLLs to IL2CPP-compiled game. I am thinking about `Assembly.LoadFrom("Mod.dll")`.
Ah, I was thinking native DLLs (which is what we're using on a project I'm working on). I think you're right that it's impossible for an IL2CPP-built player to interoperate with a managed (Mono) DLL.
>If you have some suggestions [re: serialization], I'd be interested to see what options are out there for C#.
We wrote a custom, garbage-free JSON serializer/deserializer that uses a fluent API style. We also explored a custom codegen solution (similar to FlatBuffers or protobuf) but abandoned it because the expected perf (and ergonomic) benefits would have been minor. The trickiest part with Unity codegen is generating code that creates little to no garbage.
Per the separation, I think this was far more common both in older unity games, and also professional settings.
For games shipping on mono on steam, that statistic isn't surprising to me given the amount of indie games on there and Unity's prevalence in that environment. My post in general can be read in a professional setting (ie, career game devs). The IL injection is a totally reasonable consideration, but does (currently) lock you out of platforms where AoT is a requirement. You can also support mods/DLC via addressables, and there has been improvement of modding tools for il2cpp, however you're correct it's not nearly as easy.
Going to completely disagree that Burst and HPC# are unnecessary and messy. This is for a few reasons. The restrictions that HPC# enforce essentially are the same you already have if you want to write performant C# code as you just simply use Unity's allocators for your memory up front and then operate on those. Depending on how you do this, you either can eliminate your per frame allocations, or likely eliminate some of the fragmentation you were referring to. Modern .Net is fast, of course, but it's not burst compiled HPC# fast. There are so many things that the compiler and LLVM can do based on those assumptions. Agreed C# strings are always a pain if you actually need to interpolate things at runtime. We always try to avoid these as much as we can, and intern common ones.
The fragmentation you mention on after large operations is (in my experience) indicative of save/load systems, or possibly level init code that do tons of allocations causing that to froth up. That or tons of reflection stuff, which is also usually nono for runtime perf code. The memory profiler used to have a helpful fragmentation view for that, but Unity removed it unfortunately.
> In 2018, Unity engineers discussed that they are working on porting the engine to .NET CoreCLR
Hard task, no doubt. Unity needs to throw everything at this problem. C# in general has gotten insanely fast by default. It's very much worth taking the time to upgrade/update.
Whilst we don't compare in size and api surface, it took us a few months to get off 472 and onto dotnet6. But once we were on dotnet6, moving to the LTS after that was relatively painless; usually a few hours of work.
While it’s easy to get in and make something (it’s got all the bells and whistles) it also suffers from the monolith problem (too many features, old code, tech debt).
The asset store is gold but their tech feels less refined. It’s leaps and bounds where it was when it started but it still has this empty feel to it without heavy script modifications.
There is the problem. The scripting ending designed around mono doesn’t translate as well to CoreCLR and using the same Behavior interface gets a little more complicated.
There are times (even with my own engine) that one must let go of the old and begin a new. Dx7->dx9, dx9->opengl, opengl->vulkan, vulkan->webgpu.
EDIT
I was just thinking, returning to this a couple of minute later, that if Unity wanted to prove they really care about their Core, they would introduce a complete revamp of the editor like Blender did for 3.X. Give more thought to the level editors and prefab makers. Give different workflow views. Editing / Animation / Scripting / Rendering / Post
As it stands now, it’s all just a menu item that spawns a thing in a single view with a 1999 style property window on the side like Visual Studio was ever cool.
That last step is nonsensical: WebGPU is a shim layer that Vulkan-like layer (in the sense that WebGL is GLES-like) that allows you to use the native GPGPU-era APIs of your OS.
On a "proper OS", your WebGPU is 1:1 translating all calls to Vulkan, and doing so pretty cheaply. On Windows, your browser will be doing this depending on GPU vendor: Nvidia continues to have not amazing Vulkan performance, even in cases where the performance should be identical to DX12; AMD does not suffer from this bug.
If you care about performance, you will call Vulkan directly and not pay for the overhead. If you care about portability and/or are compiling to a WASM target, you're pretty much restricted to WebGPU and you have to pay that penalty.
Side note: Nothing stops Windows drivers or Mesa on Linux from providing a WebGPU impl, thus browsers would not need their own shim impl on such drivers and there would be no inherent translation overhead. They just don't.
I wouldn't call it nonsensical to target WebGPU. If you aren't on the bleeding edge for features, its overhead is pretty low and there's value in having one perfectly-consistent API that works pretty well everywhere. (Similar to OpenGL)
I'm not killing it, but there is no C API written verbatim. WebGL was fucky because it was a specific version of GLES that never changed and you couldn't actually do GL extensions; it was a hybrid of 2.0 and 3.0 and some extra non-core/ARB extensions.
WebGPU is trying to not repeat this mistake, but it isn't a 100% 1:1 translation for Vulkan, so everyone is going to need to agree to how the C API looks, and you know damned well Google is going to fuck this up for everyone and any attempt is going to die.
The problem is the same as it was 20 years ago. There’s 2 proprietary API’s and then there’s the “open” one.
I’m sick of having to write code that needs to know the difference. There’s only a need for a Render Pass, a Texture handle, a Shader program, and Buffer memory. The rest is implementation spaghetti.
I know the point you’re making but you’re talking to the wrong person about it. I know all the history. I wish for a simpler world where a WebGPU like API exists for all platforms. I’m working on making that happen. Don’t distract.
I think the major problem with Unity is they're just rudderless. They just continue to buy plugins and slap in random features but it's really just in service of more stickers on the box and not a wholistic plan.
They've tried and failed to make their own games and they just can't do it. That means they don't have the internal drive to push a new design forward. They don't know what it takes to make a game. They just listen to what people ask for in a vacuum and ship that.
A lot of talented people at Unity but I don't expect a big change any time soon.
The talent left ship years ago. The core engine’s graphics team is all that’s really left.
They also hired Jim Whitehurst as CEO after the previous CEO crapped the bed. Then Jim left as he just didn’t understand the business (he’s probably the one responsible for the “just grab it from the store” attitude). Now they have this stinking pile of legacy they can’t get rid of.
Yeah, I started a project in Unity a while ago, and tried out Godot in the meantime.
Unity really feels like there should be a single correct way to do any specific thing you want, but actually it misses <thing> for your use case so you have to work around it, (and repeat this for every unity feature basically)
Godot on the other hand, really feels like you are being handed meaningful simple building blocks to make whatever you want.
Bingo. They don’t actually understand their users. Instead they’re the Roblox of game making, just provide the ability and let devs figure it out (and then sell it as a script).
The article doesn't cover it but the GC being used by Unity also performs very poorly vs. .NET, and even vs. standalone Mono, because it is using the Boehm GC. Last I heard Unity has no plans to switch IL2CPP to a better GC [1].
It'll be interesting to see how the CoreCLR editor performs. With that big of a speed difference the it might be possible for games to run better in the editor than a standalone Mono/IL2CPP build.
Re. the editor speedup, it should outright eliminate the "domain reload" thingy that happens because all of the C# needs to be unloaded and reloaded in response to a change.
Pretty sure that will still be there? It'll be different because CoreCLR doesn't really have AppDomains but it will still need to unload old assemblies and reload them all again. That's the only reliable way to reset everything into a clean state.
Yes, SGen should be a lot better, but Unity cannot use it because they hold and pass raw pointers around everywhere. That's fine for Boehm but not possible with SGen. They're working on fixing this already but not sure why they aren't planning a move to a better GC.
Yeah I think Unity just doesn't have the technical skillset anymore to make the migration to coreclr. It keeps getting delayed and their tech leads keep dropping out.
Might I suggest https://github.com/stride3d/stride, which is already on .net 10 and doesn't have any cross-boundary overhead like Unity.
Maybe they are making progress. But given that they first started talking about this in 2018, and then in 2022 they announced that they were planning to release a version with CoreCLR in 2023, and then in 2024 they said it would be in beta in 2025, and now in 2025 they're planning to release it as a technical preview in 2026, but they're still talking about an "internal proof-of-concept" as though it's something coming in the future...
As an outsider, it certainly seems like there's reason for skepticism.
I've seen similar things from the inside in other companies. Even existential threats (like lack of Apple Silicon support for performance-critical software), getting heavily delayed because the feature treadmill won't stop and the actually important thing is a pet project of some engineer. It is basically death by a thousand papercuts, where nobody can say what the focus is.
People complain about web development but working with native apps can be depressing sometimes.
Well they made some business decisions in the middle of that timeline that cut their funds quite a bit, not to mention probably scared off some good talent.
Not just probably scared off some good talent, they had xoofx leave over disagreements with higher management. xoofx was one of their most senior devs, the guy who started the CoreCLR migration and was leading it.
They'll get there eventually, but the current roadmap says experimental CoreCLR in late 2026, which then in the best case means production ready in 2027. Unity isn't going anywhere, but at least as a dev who doesn't care about mobile (which is Unity's real market), competing engines have gotten much more attractive in the last couple years and that seems set to continue.
Godot is the only real open source competitor, their C# support is spotty. If I can't build to Web it's useless for game jams as no one should be downloading and running random binaries.
A real sandbox solution with actual GPU support is needed.
Writing C# in godot is a bad choice. Use GDScript and directly write c++ as a module. Skip "HD extension" completely. Godots build system is easy enough to use. Just add new classes to the engine using c++ if you don't want to use GDScript. The GDScript workflow is honestly great. Using C# is like the worst of all worlds.
That's just the start. The C++ build system and package managers are the stuff if nightmares. Modern languages are significantly easier to use.
Don't get me wrong, if you offer a job with a 200k base salary and give me 6 months to learn C++ I'll do it. But I won't enjoy it, and I definitely won't do it as a hobby.
If you use an existing template (and are willing to use scons) GDExtension doesn't really have the standard build problems of rigging everything up with CMake/etc in my experience. The template is set up to handle the "set up the build" problem for you. Still have the header problem though cannot deny that one.
I've seen this issue before, they're making progress but theirs no firm release date.
Plus you then have to extensive testing to see what works in Web builds and what doesn't. I REALLY enjoy vibe coding in Godot, but it's still behind Unity in a lot of ways.
I recently started learning Godot and learning that they use .NET for the C# runtime is a nice touch. I write a lot of code that targets .NET in my day job, so having to learn the unity way of doing things would be frustrating.
Good article but seems strange that author benchmarked debug builds first, that’s a huge “no-no” in any perf tweaking and it’s clear that authors knows this well
From my experience, performance gains seen in Debug builds in Unity/C#/Mono nearly always translate in gains in Release mode. I know that this is not always true, but in this context that's my experience.
Setting up release benchmarks is much more complex and we develop the game in Debug mode, so it is very natural to get the first results there, and if promising, validate them in Release.
Also, since our team works in Debug mode, even gains that only speed things up in Debug mode are valuable for us, but I haven't encountered a case where I would see 20%+ perf gain in Debug mode that would not translate to Release mode.
That's interesting. I made measurements with Mono and CoreCLR some years ago, but only with a single thread, and I came to the conclusion that their performance was essentially the same (see https://rochus.hashnode.dev/is-the-mono-clr-really-slower-th...). Can someone explain what benchmarks were actually used? Was it just the "Simple benchmark code" in listing 1?
I think a lot of the devil is in the details, especially when we look at NET8/NET10 and the various other 'boosts' they have added to code.
But also, as far as this article, it's noting a noting a more specific use case that is fairly 'real world'; Reading a file (I/O), doing some form of deserialization (likely with a library unless format is proprietary) and whatever 'generating a map' means.
Again, this all feels pretty realistic for a use case so it's good food for thought.
> Can someone explain what benchmarks were actually used?
This honestly would be useful in the article itself, as well as, per above, some 'deep dives' into where the performance issues were. Was it in file I/O (possibly Interop related?) Was it due to some pattern in the serialization library? Was it the object allocation pattern (When I think of C# code friendly for Mono I think of Cysharp libraries which sometimes do curious things)? Not diving deeper into the profiling doesn't help anyone know where the focus needs to be (unless it's a more general thing, in which case I'd hope for a better deep dive on that aspect.)
Edited to add:
Reading your article again, I wonder whether your compiler is just not doing the right things to take advantage of the performance boosts available via CoreCLR?
E.x. can you do things like stackalloc temp buffers to avoid allocation, and does the stdlib do those things where it is advantageous?
Also, I know I vaguely hit on this above, but also wondering whether the IL being done is just 'not hitting the pattern'. where a lot of CoreCLR will do it's best magic if things are arranged a specific way in IL based on how Roslyn outputs, but even for the 'expected' C# case, deviations can lead to breaking the opt.
The goal of my compiler is not to get out maximum performance, neither of CoreCLR nor Mono. Just look at it as a random compiler which is not C#, especially not MS's C# which is highly in sync and optimized for specific features of the CoreCLR engine (which might appear in a future ECMA-335 standard). So the test essentially was to see what both, CoreCLR and Mono, do with non-optimized CIL generated by not their own compiler. This is a legal test case because ECMA-335 and its compatible CLR were exactly built for this use-case. Yes, the CIL output of my compiler could be much more improved, and I could even get more performance out of e.g. CoreCLR by using the specific knowledge of the engine (which you cannot find in the standard) which also the MS C# compiler uses. But that was not my goal. Both engine got the same CIL code and I just measured how fast it run on both engines on the same machine.
Will the move to CoreCLR give any speed ups in practice if the release build is complied with IL2CPP anyway? On all the games that I've worked on, IL2CPP is one of the first things that we've enabled, and the performance difference between the editor and release version is very noticeable.
The author (probably unknowingly) glosses over a lot in these sentences of the "How did we get here" section:
> Unity uses the Mono framework to run C# programs and back in 2006 it was one of the only viable multi-platform implementations of .NET. Mono is also open-source, allowing Unity to do some tweaks to better suit game development. [...] An interesting twist happened nearly 10 years later.
Not mentioned is that Mono itself of course improved a lot over the years, and even prior to MS's about-face on open source, it was well known that Unity was hindered by sticking with an old and out-of-date Mono, and they were very successful at deflecting the blame for this by throwing the Mono folks under the bus. Any time complaints about game developers' inability to use newer C# features came up, Mono/Xamarin would invariably receive the ire* because Unity couldn't come to an agreement with them about their license and consulting fees. (Mono was open source under LGPL instead of MIT licensed at the time, and Unity had initially bought a commercial license that allowed them exemptions from even the soft copyleft provisions in the LGPL, but the exemption was limited and not, for example, for any and all future versions, too, indefinitely.) Reportedly, they were trying to charge too much (whatever that means) for Unity's attempts to upgrade to the modern versions.
It's now 10+ years later, and now not only Mono (after being relicensed under MIT) but .NET CoreCLR are both available for Unity at no cost, but despite this it still took Unity years before they'd upgraded their C# language support and to a slightly more modern runtime.
Something else to note: Although, LGPL isn't inherently incompatible with commercial use or even use in closed source software, one sticking point was that some of the platforms Unity wished to be able to deploy have developer/publisher restrictions that are incompatible with the soft copyleft terms in the LGPL that require that users (or in this case game developers) be allowed to relink against other versions of the covered software (including, for example, newer releases). Perversely, it's because Unity sought and obtained exemptions to the LGPL that both end users and game developers were hamstrung and kept from being able to upgrade Mono themselves! (It wouldn't have helped on, say, locked down platforms like Nintendo's for example, but certainly would have been viable on platforms without the first-party restrictions, like PC gaming or Android.)
By now, Unity has gone on to pull a lot of other shenanigans with their own pricing that seems to have sufficiently pissed off the game development community, but it should still not be forgotten when they were willing to pass the blame to an open source project over the development/support that the company felt it was entitled to for a price lower than they were told it would cost, and that they themselves were slow to make progress on even when the price of the exemption literally became $0.
* you can find threads with these sorts of comments from during this period right here on HN, too, but it was everywhere
It's not AI, here's why: (yup, LLM-style pun intended)
1. Uses "I"
2. Look how many times it switches verb tenses here:
"One day I was debugging an issue in map generation and it was time-consuming [...]. To make debugging faster, I’ve written a unit test, hoping to cut down on the turn-around time since Unity takes 15+ seconds just to crunch new DLLs [...]. When I ran the test, it finished in 40 seconds."
There are few things off with the this post that essentially sound as someone more green when it comes to Unity development (no problem, we all start somewhere).
1. The stated approach of separating the simulation and presentation layers isn't all that uncommon, in fact it was the primary way of achieving performance in the past (though, you usually used C++, not C#).
2. Most games don't ship on the mono backend, but instead on il2cpp (it's hard to gauge how feasible that'd be from this post as it lacks details).
3. In modern Unity, if you want to achieve performance, you'd be better off taking the approach of utilizing the burst compiler and HPC#, especially with what appears to be happening in the in sample here as the job system will help tremendously.
4. Profiling the editor is always a fools errand, it's so much slower than even a debug build for obvious reasons.
Long story short, Unity devs are excited for the mentioned update, but it's for accessing modern language features, not particularly for any performance gains. Also, I've seen a lot of mention around GC through this comment section, and professional Unity projects tend to go out of their way to minimize these at runtime, or even sidestep entirely with unmanaged memory and DOTS.
> approach of separating the simulation and presentation layers isn't all that uncommon
I agree that some level of separation is is not that uncommon, but games usually depend on things from their respective engine, especially on things like datatypes (e.g. Vector3) or math libraries. The reason I mention that our game is unique in this way is that its non-rendering code does not depend on any Unity types or DLLs. And I think that is quite uncommon, especially for a game made in Unity.
> Most games don't ship on the mono backend, but instead on il2cpp
I think this really depends. If we take absolute numbers, roughly 20% of Unity games on Steam use IL2CPP [1]. Of course many simple games won't be using it so the sample is skewed is we want to measure "how many players play games with IL2CPP tech". But there are still many and higher perf of managed code would certainly have an impact.
We don't use IL2CPP because we use many features that are not compatible with it. For example DLC and mods loading at runtime via DLLs, reflection for custom serialization, things like [FieldOffset] for efficient struct packing and for GPU communication, etc.
Also, having managed code makes the game "hackabe". Some modders use IL injection to be able to hook to places where our APIs don't allow. This is good and bad, but so far this allowed modders to progress faster than we expected so it's a net positive.
> In modern Unity, if you want to achieve performance, you'd be better off taking the approach of utilizing the burst compiler and HPC#
Yeah, and I really wish we would not need to do that. Burst and HPC# are messy and add a lot of unnecessary complexity and artificial limitations.
The thing is, if Mono and .NET were both equally "slow", then sure, let's do some HPC# tricks to get high performance, but it is not! Modern .NET is fast, but Unity devs cannot take advantage of it, which is frustrating.
By the way, the final trace with parallel workers was just C#'s workers threads and thread pool.
> Profiling the editor is always a fools errand
Maybe, but we (devs) spend 99% of our time in the editor. And perf gains from editor usually translate to the Release build with very similar percentage gains (I know this is generally not true, but in my experience it is). We have done many significant optimizations before and measurements from the editor were always useful indicator.
What is not very useful is Unity's profiler, especially with "deep profile" enabled. It adds constant cost per method, highly exaggerating cost of small methods. So we have our own tracing system that does not do this.
> I've seen a lot of mention around GC through this comment section, and professional Unity projects tend to go out of their way to minimize these at runtime
Yes, minimizing allocations is key, but there are many cases where they are hard to avoid. Things like strings processing for UI generates a lot of garbage every frame. And there are APIs that simply don't have an allocation-free options. CoreCLR would allow to further cut down on allocations and have better APIs available.
Just the fact that the current GC is non-moving means that the memory consumption goes up over time due to fragmentation. We have had numerous reports of "memory" leaks where players report that after periodic load/quit-to-menu loops, memory consumption goes up over time.
Even if we got fast CoreCLR C# code execution, these issues would prevail, so improved CG would be the next on the list.
[1] https://steamdb.info/stats/releases/?tech=SDK.UnityIL2CPP
FieldOffset is supported by IL2CPP at compile time [0]. You can also install new DLLs and force the player to restart if you want downloadable mod support.
It's true that you can't do reflection for serialization, but there are better, more performant alternatives for that use case, in my experience.
[0] https://docs.unity3d.com/Manual/scripting-restrictions.html
I am not aware of an easy way to load (managed) mods as DLLs to IL2CPP-compiled game. I am thinking about `Assembly.LoadFrom("Mod.dll")`.
Can you elaborate how this is done?
> there are better, more performant alternatives for that use case, in my experience.
We actually use reflection to emit optimal code for generic serializers that avoid boxing and increase performance.
There may be alternatives, we explored things like FlatBuffers and their variants, but nothing came close to our system in terms of ease of use, versioning support, and performance.
If you have some suggestions, I'd be interested to see what options are out there for C#.
> FieldOffset is supported by IL2CPP at compile time
You are right, I miss-remembered this one, you cannot get it via reflection, but it works.
Ah, I was thinking native DLLs (which is what we're using on a project I'm working on). I think you're right that it's impossible for an IL2CPP-built player to interoperate with a managed (Mono) DLL.
>If you have some suggestions [re: serialization], I'd be interested to see what options are out there for C#.
We wrote a custom, garbage-free JSON serializer/deserializer that uses a fluent API style. We also explored a custom codegen solution (similar to FlatBuffers or protobuf) but abandoned it because the expected perf (and ergonomic) benefits would have been minor. The trickiest part with Unity codegen is generating code that creates little to no garbage.
Now I think about it, writing SourceGenerators is actually a great fit for AI agents.
Per the separation, I think this was far more common both in older unity games, and also professional settings.
For games shipping on mono on steam, that statistic isn't surprising to me given the amount of indie games on there and Unity's prevalence in that environment. My post in general can be read in a professional setting (ie, career game devs). The IL injection is a totally reasonable consideration, but does (currently) lock you out of platforms where AoT is a requirement. You can also support mods/DLC via addressables, and there has been improvement of modding tools for il2cpp, however you're correct it's not nearly as easy.
Going to completely disagree that Burst and HPC# are unnecessary and messy. This is for a few reasons. The restrictions that HPC# enforce essentially are the same you already have if you want to write performant C# code as you just simply use Unity's allocators for your memory up front and then operate on those. Depending on how you do this, you either can eliminate your per frame allocations, or likely eliminate some of the fragmentation you were referring to. Modern .Net is fast, of course, but it's not burst compiled HPC# fast. There are so many things that the compiler and LLVM can do based on those assumptions. Agreed C# strings are always a pain if you actually need to interpolate things at runtime. We always try to avoid these as much as we can, and intern common ones.
The fragmentation you mention on after large operations is (in my experience) indicative of save/load systems, or possibly level init code that do tons of allocations causing that to froth up. That or tons of reflection stuff, which is also usually nono for runtime perf code. The memory profiler used to have a helpful fragmentation view for that, but Unity removed it unfortunately.
Hard task, no doubt. Unity needs to throw everything at this problem. C# in general has gotten insanely fast by default. It's very much worth taking the time to upgrade/update.
Whilst we don't compare in size and api surface, it took us a few months to get off 472 and onto dotnet6. But once we were on dotnet6, moving to the LTS after that was relatively painless; usually a few hours of work.
While it’s easy to get in and make something (it’s got all the bells and whistles) it also suffers from the monolith problem (too many features, old code, tech debt).
The asset store is gold but their tech feels less refined. It’s leaps and bounds where it was when it started but it still has this empty feel to it without heavy script modifications.
There is the problem. The scripting ending designed around mono doesn’t translate as well to CoreCLR and using the same Behavior interface gets a little more complicated.
There are times (even with my own engine) that one must let go of the old and begin a new. Dx7->dx9, dx9->opengl, opengl->vulkan, vulkan->webgpu.
EDIT I was just thinking, returning to this a couple of minute later, that if Unity wanted to prove they really care about their Core, they would introduce a complete revamp of the editor like Blender did for 3.X. Give more thought to the level editors and prefab makers. Give different workflow views. Editing / Animation / Scripting / Rendering / Post
As it stands now, it’s all just a menu item that spawns a thing in a single view with a 1999 style property window on the side like Visual Studio was ever cool.
On a "proper OS", your WebGPU is 1:1 translating all calls to Vulkan, and doing so pretty cheaply. On Windows, your browser will be doing this depending on GPU vendor: Nvidia continues to have not amazing Vulkan performance, even in cases where the performance should be identical to DX12; AMD does not suffer from this bug.
If you care about performance, you will call Vulkan directly and not pay for the overhead. If you care about portability and/or are compiling to a WASM target, you're pretty much restricted to WebGPU and you have to pay that penalty.
Side note: Nothing stops Windows drivers or Mesa on Linux from providing a WebGPU impl, thus browsers would not need their own shim impl on such drivers and there would be no inherent translation overhead. They just don't.
WebGPU is trying to not repeat this mistake, but it isn't a 100% 1:1 translation for Vulkan, so everyone is going to need to agree to how the C API looks, and you know damned well Google is going to fuck this up for everyone and any attempt is going to die.
Chrome is the cancer killing desktop computing.
The problem is the same as it was 20 years ago. There’s 2 proprietary API’s and then there’s the “open” one.
I’m sick of having to write code that needs to know the difference. There’s only a need for a Render Pass, a Texture handle, a Shader program, and Buffer memory. The rest is implementation spaghetti.
I know the point you’re making but you’re talking to the wrong person about it. I know all the history. I wish for a simpler world where a WebGPU like API exists for all platforms. I’m working on making that happen. Don’t distract.
They've tried and failed to make their own games and they just can't do it. That means they don't have the internal drive to push a new design forward. They don't know what it takes to make a game. They just listen to what people ask for in a vacuum and ship that.
A lot of talented people at Unity but I don't expect a big change any time soon.
They also hired Jim Whitehurst as CEO after the previous CEO crapped the bed. Then Jim left as he just didn’t understand the business (he’s probably the one responsible for the “just grab it from the store” attitude). Now they have this stinking pile of legacy they can’t get rid of.
Unity really feels like there should be a single correct way to do any specific thing you want, but actually it misses <thing> for your use case so you have to work around it, (and repeat this for every unity feature basically)
Godot on the other hand, really feels like you are being handed meaningful simple building blocks to make whatever you want.
It'll be interesting to see how the CoreCLR editor performs. With that big of a speed difference the it might be possible for games to run better in the editor than a standalone Mono/IL2CPP build.
[1] https://discussions.unity.com/t/coreclr-and-net-modernizatio...
For what reason? Mono has a pretty good precise GC since many years.
Might I suggest https://github.com/stride3d/stride, which is already on .net 10 and doesn't have any cross-boundary overhead like Unity.
Unity updates on their plans and progress:
2022 - officially announced plans to switch to CoreCLR - https://unity.com/blog/engine-platform/unity-and-net-whats-n...
2023 - Tech update - https://unity.com/blog/engine-platform/porting-unity-to-core...
Unite 2025 - CoreCLR based player scheduled for Unity 6.7 in 2026 - https://digitalproduction.com/2025/11/26/unitys-2026-roadmap...
As an outsider, it certainly seems like there's reason for skepticism.
People complain about web development but working with native apps can be depressing sometimes.
They'll get there eventually, but the current roadmap says experimental CoreCLR in late 2026, which then in the best case means production ready in 2027. Unity isn't going anywhere, but at least as a dev who doesn't care about mobile (which is Unity's real market), competing engines have gotten much more attractive in the last couple years and that seems set to continue.
Godot is the only real open source competitor, their C# support is spotty. If I can't build to Web it's useless for game jams as no one should be downloading and running random binaries.
A real sandbox solution with actual GPU support is needed.
It's very difficult to me, I generally stick to high level stuff , C#, JavaScript, Python, Dart, etc.
Not denigrating, genuine question.
Headers.
That's just the start. The C++ build system and package managers are the stuff if nightmares. Modern languages are significantly easier to use.
Don't get me wrong, if you offer a job with a 200k base salary and give me 6 months to learn C++ I'll do it. But I won't enjoy it, and I definitely won't do it as a hobby.
I've seen this issue before, they're making progress but theirs no firm release date.
Plus you then have to extensive testing to see what works in Web builds and what doesn't. I REALLY enjoy vibe coding in Godot, but it's still behind Unity in a lot of ways.
But C# is what I've used for over a decade. C# has vastly better IDE support. It's a more complete language.
Plus a lot of C# assets/libraries don't really have GDScript counterparts.
I would have estimated a year, or two tops, for that project.
Setting up release benchmarks is much more complex and we develop the game in Debug mode, so it is very natural to get the first results there, and if promising, validate them in Release.
Also, since our team works in Debug mode, even gains that only speed things up in Debug mode are valuable for us, but I haven't encountered a case where I would see 20%+ perf gain in Debug mode that would not translate to Release mode.
But also, as far as this article, it's noting a noting a more specific use case that is fairly 'real world'; Reading a file (I/O), doing some form of deserialization (likely with a library unless format is proprietary) and whatever 'generating a map' means.
Again, this all feels pretty realistic for a use case so it's good food for thought.
> Can someone explain what benchmarks were actually used?
This honestly would be useful in the article itself, as well as, per above, some 'deep dives' into where the performance issues were. Was it in file I/O (possibly Interop related?) Was it due to some pattern in the serialization library? Was it the object allocation pattern (When I think of C# code friendly for Mono I think of Cysharp libraries which sometimes do curious things)? Not diving deeper into the profiling doesn't help anyone know where the focus needs to be (unless it's a more general thing, in which case I'd hope for a better deep dive on that aspect.)
Edited to add:
Reading your article again, I wonder whether your compiler is just not doing the right things to take advantage of the performance boosts available via CoreCLR?
E.x. can you do things like stackalloc temp buffers to avoid allocation, and does the stdlib do those things where it is advantageous?
Also, I know I vaguely hit on this above, but also wondering whether the IL being done is just 'not hitting the pattern'. where a lot of CoreCLR will do it's best magic if things are arranged a specific way in IL based on how Roslyn outputs, but even for the 'expected' C# case, deviations can lead to breaking the opt.
I don't beleive such a large regression from .NET framework to CoreCLR.
> Unity uses the Mono framework to run C# programs and back in 2006 it was one of the only viable multi-platform implementations of .NET. Mono is also open-source, allowing Unity to do some tweaks to better suit game development. [...] An interesting twist happened nearly 10 years later.
Not mentioned is that Mono itself of course improved a lot over the years, and even prior to MS's about-face on open source, it was well known that Unity was hindered by sticking with an old and out-of-date Mono, and they were very successful at deflecting the blame for this by throwing the Mono folks under the bus. Any time complaints about game developers' inability to use newer C# features came up, Mono/Xamarin would invariably receive the ire* because Unity couldn't come to an agreement with them about their license and consulting fees. (Mono was open source under LGPL instead of MIT licensed at the time, and Unity had initially bought a commercial license that allowed them exemptions from even the soft copyleft provisions in the LGPL, but the exemption was limited and not, for example, for any and all future versions, too, indefinitely.) Reportedly, they were trying to charge too much (whatever that means) for Unity's attempts to upgrade to the modern versions.
It's now 10+ years later, and now not only Mono (after being relicensed under MIT) but .NET CoreCLR are both available for Unity at no cost, but despite this it still took Unity years before they'd upgraded their C# language support and to a slightly more modern runtime.
Something else to note: Although, LGPL isn't inherently incompatible with commercial use or even use in closed source software, one sticking point was that some of the platforms Unity wished to be able to deploy have developer/publisher restrictions that are incompatible with the soft copyleft terms in the LGPL that require that users (or in this case game developers) be allowed to relink against other versions of the covered software (including, for example, newer releases). Perversely, it's because Unity sought and obtained exemptions to the LGPL that both end users and game developers were hamstrung and kept from being able to upgrade Mono themselves! (It wouldn't have helped on, say, locked down platforms like Nintendo's for example, but certainly would have been viable on platforms without the first-party restrictions, like PC gaming or Android.)
By now, Unity has gone on to pull a lot of other shenanigans with their own pricing that seems to have sufficiently pissed off the game development community, but it should still not be forgotten when they were willing to pass the blame to an open source project over the development/support that the company felt it was entitled to for a price lower than they were told it would cost, and that they themselves were slow to make progress on even when the price of the exemption literally became $0.
* you can find threads with these sorts of comments from during this period right here on HN, too, but it was everywhere
1. Uses "I"
2. Look how many times it switches verb tenses here:
"One day I was debugging an issue in map generation and it was time-consuming [...]. To make debugging faster, I’ve written a unit test, hoping to cut down on the turn-around time since Unity takes 15+ seconds just to crunch new DLLs [...]. When I ran the test, it finished in 40 seconds."