4 ms·
This looks great! I’m curious if C# game dev has issues with GC pauses.
by tmountain 3y ago
This looks great! I’m curious if C# game dev has issues with GC pauses.
- neonsunset 3y agoGC issues pretty much exist in Unity land only. Other game engines which use vanilla .NET like Stride work without a hitch.
- VHRanger 3y agoDoes the C# GC have guarantees about when it will/won't run? Dlangs GC for instance guarantee that it will only run on allocation of new memory so it's possible to architect the game to avoid meaningful pauses
- neonsunset 3y agoIn .NET, GC is triggered when a user thread runs out of memory in an allocation budget and needs more, similar to what you described [0]. Generally speaking, indefinitely preventing GC from running is not possible (you always end up putting some data on the heap) therefore an optimal strategy is similar to any other language - limiting allocations and reusing memory through object and array pooling. This will ensure that GC pauses are as infrequent and as short as possible. It's important to note that if there is sufficient allocation budget - the GC will not run. This way, in a well written code the GC may only ever trigger every few hundred frames and only take a millisecond to run. In fact, OSU! has been able to get consistently good frame times even back on .NET Framework. [0] https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/garbage-collection.md https://github.com/dotnet/runtime/blob/main/docs/design/core... [1] https://maoni0.medium.com/dynamically-adapting-to-application-sizes-2d72fcb6f1ea https://maoni0.medium.com/dynamically-adapting-to-applicatio...
- runevault 3y agoI wonder how much of the extra issues Unity has is because it is a very old version of Mono, since more modern versions of .NET have become so much more performant (especially Core 3.1 into 5+). I was sad when .NET 8 came out too late for Godot to fit in the update for Godot 4.2, so I believe they intend to make the update for 4.3.
- DannyWebbie 3y agoUnity is leaving quite a bit on the table by not being on a modern version of .NET. Unity's C++ side seems to have (or had) assumptions about the garbage collection. Progress and challenges on .NET in Unity can be seen in this frequently updated forum thread https://forum.unity.com/threads/unity-future-net-development-status.1092205/ https://forum.unity.com/threads/unity-future-net-development... Unity's long term plan appears to become more tightly integrated with .NET and to avoid maintaining their own .NET stuff.
- runevault 3y agoYeah back before I switched to Godot for my game dev dabbling I was keeping an eye on info about the upgrade to a more modern version of .NET, but I got less and less interested and saw Godot 4 was going to have .NET 6 out the gate so just switched (and all of this was before Unity went full idiot which fully removed any interest I have in going back).
- gentleman11 3y agoYes. You have to allocate at the start and use object pooling, in unity and even unreal (unreal has garbage collection too)
- zengid 3y agoYes and here is a really great talk from Miguel de Icaza about some of the history of adding C# to game engines, and why he's excited about using Swift in Godot because he claims that Swift's reference counting avoids the issues of GC pauses. https://youtu.be/tzt36EGKEZo?si=WNxwcTPGFbJABKez https://youtu.be/tzt36EGKEZo?si=WNxwcTPGFbJABKez
- pjmlp 3y agoBased on his experience with Mono, less so with .NET proper. Also given the state of Swift outside Apple ecosystem, I guess he only plans to target developers on their platforms.
- TillE 3y agoDe Icaza suggests that the GC hasn't improved sufficiently even in the modern .NET era, and maybe that's true in certain cases. But in practice, dozens of big-budget 3D games have been made with Unity, using a janky old Mono runtime that performs far worse than .NET 8. Swift is a really interesting language, and Apple does distribute official toolchain binaries for Linux and Windows. But yeah it's still a bit of an awkward choice if you're not using Xcode, hopefully that improves.
- neonsunset 3y agoThere is no such thing as a free lunch :) Modern GC implementations offer an important advantage - very cheap allocations and batched deallocations which allow to sustain high allocation traffic that goes through the heap. This comes at a cost of non-deterministic memory usage and object de-allocation. Further tradeoff is made between GC pause times, allocation throttling (Go) and even higher memory footprint (ZGC). My knowledge on the exact overhead of Swift's reference counting is very limited but there's a good chance it comes with significant upfront cost that you don't have to pay in languages with GC or with compile-time-defined allocation semantics (Rust and C++ RAII). There's a reason why Apple invested in atomics being as cheap[0] as they are on their ARM64 cores. Overall, every time I find Swift benchmark numbers on the internet, they turn out to be far[1][2] from near-Rust performance despite being a language built on top of LLVM. [0] https://dougallj.github.io/applecpu/firestorm-int.html https://dougallj.github.io/applecpu/firestorm-int.html (e.g. CAS, CASAL, DMB) [1] https://github.com/ixy-languages/ixy-languages https://github.com/ixy-languages/ixy-languages [2] https://github.com/jinyus/related_post_gen#multicore-results https://github.com/jinyus/related_post_gen#multicore-results
- unclad5968 3y agoYou can write C# to avoid allocations like most other languages
- bob1029 3y agoI've had strong success with trivial object management & explicitly invoking GC on a delay that is some small multiple of the simulation delay. The sweet spot seems to depend on scene content and style of gameplay (how the scene objects are mutated). For Q3A-style scenes and gameplay, it is extremely robust. Very few scene elements change tick by tick, so GC pauses are mostly irrelevant. More specifically with .NET6+, I am seeing GC pauses measured in the 1~5ms range in my DIY engines when I strategically call GC.Collect() on a rapidly-recurring basis. Another tweak I made was to disable concurrent/server/background GC in the project config. Foreground+workstation GC appears to provide the lowest jitter in this arrangement. My engine's main loop operates on batches of events pulled from a ring buffer abstraction, and if one of those events just so happens to be a scheduled GC, then I deal with it right there in-line on the same thread. I do not attempt to defer it to a GC thread or some other weirdness. The only strategic options seem to be to either explicitly manage all the memory 100%, or take out the trash as often as possible. You could also entertain the cruise missile GC strategy, but Microsoft has made it nearly impossible to completely disable GC in latest .NET without employing black magic low level DLL hackery. I can definitely see a situation where you don't care about GC because you can just recycle the process between business events, rounds of gameplay, etc. Process.Exit() is a viable GC technique in some domains.
- neonsunset 3y agoCruise missiles can fly for a long time! Now, ballistic ones... Either way, almost spilled the coffee over this haha.
- Thaxll 3y agoGC pauses are one thing but you have to keep in mind thata large part of your cpu time is going to be garbage collection.
- kirankp89 3y agoMy experience with Unity on consoles is that it is very much an issue but can be avoided with some careful architecture choices very early on in development. If you are not careful about allocations, the reserved managed heap size grows and GC pauses quickly deteriorate the experience. There’s also no way to reclaim memory and very soon you’ll have a giant C# heap with not enough memory for native code in engine to load textures etc. This is very hard to recover from, hence to need to be vigilant from the start.
- MattRix 3y agoThis is true but it’s worth noting it’s because Unity used Mono’s garbage collector which was, well… garbage.