Posted by iliketrains 12 hours ago
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.
Unity's whole shtick is that they make something horrible, then improve upon it marginally. The ground reality is that these performance enhancement schemes still fall very much short of just doing the basic sensible thing - using CoreCLR for most code, and writing C++ for the truly perf critical parts.
IL2Cpp is a horror-kludge of generated code, that generates low-quality C++ code from .NET IL, relying on the opitmizing compiler to extract decent performance out of it.
You can check it out: https://unity.com/blog/engine-platform/il2cpp-internals-a-to...
The resulting code gives up every possible convenience of C# (compile speed, convenience, debuggability), while falling well short of even modern .NET on performance.
The Burst compiler/HPC# plays on every meme perpetuated by modern gamedev culture (structure-of-arrays, ECS), but performance wise, generally still falls short of competently, but naively written C++ or even sometimes .NET C#. (Though tbf, most naive CoreCLR C# code is like 70-80% the speed of hyper-optimized Burst)
These technologies needless to say, are entirely proprietary, and require you to architect your code entirely their paradigms, use proprietary non-free libraries that make it unusable outside unity, and other nasty side effects.
This whole snakeoil salesmanship is enabled by these cooked Unity benchmarks that always compare performance to the (very slow) baseline Mono, not modern C# or C++ compilers.
These are well-established facts, benchmarked time and time again, but Unity marketing somehow still manages to spread the narrative of their special sauce compilers somehow being technically superior.
But it seems the truth has been catching up to them, and even they realized they have to embrace CoreCLR - which is coming soonTM in Unity. I think it's going to be a fun conversation when people realize that their regular Unity code using CoreCLR runs just as fast or faster than the kludgey stuff they spent 3 times as much time writing, that Unity has been pushing for more than a decade as the future of the engine.
> 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.
Sure, but the fact that it is competitive with Burst makes it disappointing. If I'm going to go through the trouble of writing code in a different (and not portable!) way then it better be significantly faster. Especially when most code cannot be written as Burst jobs unless you use their (new) ECS.
For modding and OTA stuff I just use a scripting language with good interop (I made OneJS partially for this purpose). No more AOT issue and no more waiting for domain reload, etc.
Making a managed code burst-compatible comes with real constraints that go beyond "write performant C#". In Burstable code, you generally can't interact with managed objects/GC-dependent APIs, so the design is pushed towards unmanaged structs in native collections. And this design spreads. The more logic is to be covered by Burst, the more things has to be broken down to native containers of unmanaged structs.
I agree that designing things in data-oriented way is good, but why to force this additional boundary and special types on devs instead of just letting them write it in C#? Writing burstable code can increase complexity, one has to manage memory/lifetimes, data layout, and job-friendly boundaries, copying data between native and managed collections, etc., not just "writing fast C#".
In a complex simulation game, my experience is that there are definitely things that fit the "raw data, batch processing" model, but not all gameplay/simulation logic does. Things like inheritance, events, graphs, AI (the dumb "game" version, no NN), UI, exceptions, etc. And on top of it all, debugging complications.
Wouldn't you be relieved with announcement: "C# is now as fast as Burst, have fun!"? You'd be able to do the same data-oriented design where necessary, but keep all the other tings handy standing by when needed. It's so close, yet, so far!
> The fragmentation you mention
What you say makes sense. I've actually spent a lot of time debugging this and I did find some "leaks" where references to "dead objects" were keeping them from being GC'd. But after sorting all these out, Unity's memory profiler was showing that "Empty Heap Space" was the culprit, that one kept increasing after every iteration. My running theory is that the heap is just more and more fragmented, and some static objects randomly scattered around it are keeping it from being shrunk. ¯\_(ツ)_/¯
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...
You tend to want zero per frame allocation as it is and that would probably not change.
As long as your less frequent garbage doesn't overtake the incremental GC, that's not really an issue either. If it's working incrementally as intended stutter shouldn't be an issue.
In a game there's no endless benefit from raw GC throughput like you might see on a server instance that could always push more requests per second.
If your game is allocating so quickly that the incremental GC can't keep up, I would argue that solving this with a "faster" GC is just taking you further into hell.
For what reason? Mono has a pretty good precise GC since many years.
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.
I don't think this is fair. I'd say Unity's inspector window is one of the good parts of Unity because it not just a property window. It's an immediate mode UI that things can hook into to do a lot more than just property editing.
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.
You can't just ship a WebGPU implementation in the driver because the last-mile of getting the <canvas> on screen is handled by the browser in entirely browser specific ways. You'd require very tight coordination between the driver and browsers, and you still wouldn't be saving much because the overhead you get from WebGPU isn't because of API translation, rather it's the cost to make the API safe to expose in a browser.
Vulkan has also evolved a lot and WebGPU doesn't want to require new Vulkan features, lacking for example bindless textures, ray tracing etc.
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.
I don't agree with you on the Asset Store, for the same reasons - the rate of breakage means that things that are not constantly updated no longer work - and multple versions need to be maintained for parallel engine versions. That combined with the dubious economics of Asset Store (I don't think it makes financial sense to even make these things, let alone maintain them), they mostly end up as abandonware.
And on the Asset Store if you make something indispensable (which is more often than not something the engine should'have OOTB, like competent text rendering), one of the following things will happen:
- Unity will buy you out, and plop your asset in the engine, without doing any integration work, and will stick out like a sore thumb (TextMeshPro). Good for you, bad for consumers, and sucks if you were making a competitor
- They build an in-house solution, that you obviously can't compete with, and will have a huge leg up on you because they have engine access (sucks to be you)
- The engine will never have that feature, because 'you can just buy it', meaning you have to either spends hundreds of dollars/euros on dubious quality assets or hunt for open-source versions with generally even more varying usability. UE4/5 has a lot of these built in, and AAA quality.
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.
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.
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.
Pointers, manual memory management, and the design by committee standard library are not fun.
I think it's overly verbose and probably has a lot of unneeded legacy stuff, but in terms of making money so I can afford donuts, it's not bad.
My personal favorite language is probably Dart, but it's basically useless outside of Flutter and I don't have faith in Google to keep Flutter going.
I don't like low level programming. My dream language is literally an LLM that processes a markdown document where I sorta just describe what I want to happen. With options to call defined functions in a normal programing language
If I had money ( VC money) I'd be working on this.
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.
Unity really do need to catch up though. .Net 8/10 is really amazing and FAST. With the new garbage collector changes for .Net 10, game stutter would be nearly gone gone.
Unrelated point: I basically stream most of my games with Sunlight + Moonlight from my main rig to my phone with a controller attached, so I can play Diablo 2 remastered, Hades, Grim Dawn and basically any game with Xbox controller support, directly on my phone. Works amazingly well. Phones has high dpi screen + 120hz OLED panel and since all rendering happens on my headless gaming server, at highest settings, the phone's battery doesn't really drain at all, can play for hours. Playing a Mono based game from the play store can drain the battery within 2 hours, and the games are usually so badly built for android, even paid games that have equivalents on steam.
My understanding is that you can (almost) do that using MAUI (formerly known as Xamarin). You will not get a .Net SDK or Runtime, but the mono runtime. Since it is bundled in your app, you won't actually notice.
I would have estimated a year, or two tops, for that project.
CoreCLR NativeAOT is already shipping real games on Nintendo, PS5, and Xbox.
JIT isn't allowed on iPhones either, and this is what NativeAOT solves. Also, .NET is moving WASM support to CoreCLR (rather than mono) in an upcoming version as well.