5 ms·
>Anything that uses classes and interfaces will be memory managed by the GC... Yes and no. Yes, almost all of the standard library collection are allocation he
by algorithmsRcool 2y ago
>Anything that uses classes and interfaces will be memory managed by the GC...
Yes and no.
Yes, almost all of the standard library collection are allocation heavy and it is still the dominate pattern in C#, so if you want to avoid the GC you need to avoid these and resort to building your own primitives based on Memory/Span. Which sucks.
However, you can use interfaces in a no GC world since you can constrain those interfaces to be structs or ref-structs and the compiler will enforce rules that prevent them from being boxed onto the GC heap.
Also of recent note, the JIT can now automagically convert simple gc-heap allocations into stack allocations if it can trivially prove they don't escape the stack context.
> It would be better if the GC can be turned off with a switch and just add a delete operator to manually free memory.
It is a little know fact that you can actually swap out the GC of the runtime. So you could plug in a null implementation that never collects (at your own peril...)
As for a delete operator, you can just roll your own struct based allocation framework that uses IDisposable to reclaim memory. But then you need to deal with all the traditional bugs like use-after-free and double-free and the like.
For me, I think low-gc is the happy medium. Avoid the heap in 99% of cases but let the GC keep things air tight
- recursive 2y ago> constrain those interfaces to be structs or ref-structs How? I know of constraints on generic type parameters, but not how to do this. A cursory search is unhelpful.
- neonsunset 2y agoI think the comment just meant using generic constraints with structs. e.g. interface Foo { int Calculate(); } static void CalculateThing<T>(T impl) where T: Foo { var num = impl.Calculate() * 2; Console.WriteLine(num); } Here if you pass a struct that implements 'Foo', 'CalculateThing' will be monomorphized and the dispatch will be zero-cost, same as in Rust. You can apply additional constraints like `where T: struct` or `allows ref struct`. The last one is a new addition which acts like a lifetime restriction that says that you are not allowed to box T because it may be a ref struct. Ref structs are for all intents and purposes regular structs that can hold so-called "managed references" aka byrefs, which have syntax 'ref T', which is discussed in detail by the article this submission links to (ref structs can also hold other ref structs, you are not limited in nesting, but you are limited in cyclicality).
- algorithmsRcool 2y agoYes, this is what I meant
- algorithmsRcool 2y ago> As for a delete operator, you can just roll your own struct based allocation framework that uses IDisposable to reclaim memory. But then you need to deal with all the traditional bugs like use-after-free and double-free and the like. I forgot that there is built in support for this model using the MemoryManager<T> class [0]. A memory manager is an abstract class that represents a block of other memory, including possibly unmanaged memory. It implements IDisposable already so you can just plug into this. The Memory<T> struct can optionally internally point to a MemoryManager instance allowing you to plug your perfered style of allocation and freeing of memory into parts of the framework. There is a little irony that a MemoryManager<T> is itself a class and therefore managed on the gc-heap, but you can defeat this by using ObjectPool<T> to recycle those instances to keep allocation count steady state and not trigger the GC. I have used this before (in the toy database i mentioned earlier) to allocate aligned blocks of unmanaged memory. [0] https://learn.microsoft.com/en-us/dotnet/api/system.buffers.memorymanager-1?view=net-8.0 https://learn.microsoft.com/en-us/dotnet/api/system.buffers....
- CraigJPerry 2y ago>> It is a little know fact that you can actually swap out the GC of the runtime. So you could plug in a null implementation How do you do this? Just so I can have another tool in my tool shed. Googling got me to an archived repo on GitHub with a sample GC - which is enough but Wonder if there’s something off the shelf. In java land, the Epsilon GC (a do nothing GC) enables a pattern that’s handy in perf test jobs in CI pipelines occasionally for some projects (I.e. run with epsilon but constrain max memory for the process - ci builds will fail if memory usage increases)
- algorithmsRcool 2y agoIt isn't trivial but it is supported. Here is an example [0] toy. I am not aware of any production grade replacement GCs for .NET out there currently https://github.com/kkokosa/UpsilonGC https://github.com/kkokosa/UpsilonGC