Live data from Hacker News

.NET GC Internals mini-series

tooslowexception.com

61–70 of 77 posts

Re: .NET GC Internals mini-series

#61
post #21

Anyone have a good comparison of .net vs java GCs? I never had trouble with the former but Java has been terrible. I'm not sure if its just my project or having the xmx memory cap.

.NET GC is nothing to write home about, pretty standard with long stop the world phase. It's not super interesting so nobody raves about it. Java's new collectors ZGC and Shenandoah are state of the art, perhaps the best GC's in the world. There's collectors with lower latency and higher throughput but nothing out there that achieves both like Java's new collectors do. It's a large part of why you hear that Java "per…

Citation needed. Overall .NET performance usually beats Java in benchmarks today , so if the GC is substantively less good, that shouldn't be possible. I've not seen GC comparisons specifically though so if you know of some recent ones let me know.

.NET has made a ton of performance progress in the last 4 years, and a lot of people's ideas about the performance differences are based on .NET from the pre .NET Core days.

Re: .NET GC Internals mini-series

#62
post #39
post #37

Earlier quoted context omitted.

The GC kicks in when allocating a new object and it decides it needs more memory. GC likely kicked in as you allocated the last object, and after the collection phase, still didn’t have enough memory and so failed. I believe objects with destructors can keep an object alive for an extra collection phase, but I believe if that’s the case it can easily be solved with a using block before allocating the next object.

No that wasn't the problem. You should be able to test it yourself for instance by opening and dereferencing a lot of system.drawing images but not disposing them. You will most likely get an out of memory exception (unless the behavior changed in the last couple of years, and I have not tested it on .net core). Unfortunately the GC doesn't immediatly kick in when you have a memory pressure and you will get an out of…

I want to point out that disposing resources (using the IDisposable) interface does not free memory (unless the memory isn't managed but then it's not related to garbage collection).

A System.Drawing.Image holds operating system resources (GDI+) and these resources are released when the Image instance is disposed. If the instance isn't explicitly disposed then the finalizer will do it but this only happens when the garbage collector collects the Image instance.

Allocating many Image instances without disposing them might exhaust the available resources (Windows bitmap handles or whatever) but the garbage collector doesn't see any memory pressure and does not perform any collection so the finalizer does not dispose Image instances that are no longer used.

The garbage collector has an API (GC.AddMemoryPressure) where an object that has unmanaged resources can signal that it's consuming additional memory to inform the garbage collector's decision of when to perform a collection.

Re: .NET GC Internals mini-series

#63
post #43
post #42

Earlier quoted context omitted.

There's an inherent issue with doing that while still being safe: what if there's still a reference to your "big object" somewhere? The only way for the runtime to know for certain it's safe to delete the object is to effectively run the GC anyway. The alternative is that the object gets deleted without any checks and any references to it will now (probably) cause a crash - and it'll be a hard native crash rather tha…

True but all it takes to fix this is to add if on the reference count of the object. You only need a full scan to handle circular references. You could have a gc.collect(obj) which will collect this object and all of its dependencies provided their reference count has gone to zero. And otherwise do nothing until the next full garbage collection.

There's no reference count on the object. That's not how garbage collection works in .NET, or Java for that matter.

Re: .NET GC Internals mini-series

#64

Earlier quoted context omitted.

.NET GC is nothing to write home about, pretty standard with long stop the world phase. It's not super interesting so nobody raves about it. Java's new collectors ZGC and Shenandoah are state of the art, perhaps the best GC's in the world. There's collectors with lower latency and higher throughput but nothing out there that achieves both like Java's new collectors do. It's a large part of why you hear that Java "per…

Citation needed. Overall .NET performance usually beats Java in benchmarks today , so if the GC is substantively less good, that shouldn't be possible. I've not seen GC comparisons specifically though so if you know of some recent ones let me know. .NET has made a ton of performance progress in the last 4 years, and a lot of people's ideas about the performance differences are based on .NET from the pre .NET Core day…

Micro benchmarks are written to avoid creating garbage since code is faster that way. C# wins these comparisons easily.

I haven't found a comparison of JVM vs CLR GC but I know the .NET team has intended to introduce a ZGC style collector for a while.

The need is not as severe in CLR because it produces less garbage.

But CLR stop the world pauses are long and it's definitely a problem for some uses cases. Many companies focus on their P99 latency these days and a 1 second GC pause will ruin those metrics. And stuff like HFT just can't be done in C# without turning GC off

Re: .NET GC Internals mini-series

#65

Earlier quoted context omitted.

.NET GC is nothing to write home about, pretty standard with long stop the world phase. It's not super interesting so nobody raves about it. Java's new collectors ZGC and Shenandoah are state of the art, perhaps the best GC's in the world. There's collectors with lower latency and higher throughput but nothing out there that achieves both like Java's new collectors do. It's a large part of why you hear that Java "per…

Citation needed. Overall .NET performance usually beats Java in benchmarks today , so if the GC is substantively less good, that shouldn't be possible. I've not seen GC comparisons specifically though so if you know of some recent ones let me know. .NET has made a ton of performance progress in the last 4 years, and a lot of people's ideas about the performance differences are based on .NET from the pre .NET Core day…

Many recent improvements in .NET performance are enabled by reducing or removing allocations in hot paths, so the GC has to do less work¹. The point was mostly that the .NET GC can get away with not being as advanced since it commonly has to do less work than in Java anyway. C# gives the developer more low-level control over what actually runs on the CPU in the end, so there's a slight shift in responsibility for performance from the JIT/GC towards the developer (although the latter two also improve, of course).

______

¹ Plus a number of algorithmic improvements and the ability to leverage specific CPU instructions that help as well.

Re: .NET GC Internals mini-series

#66
post #21

Anyone have a good comparison of .net vs java GCs? I never had trouble with the former but Java has been terrible. I'm not sure if its just my project or having the xmx memory cap.

.NET GC is nothing to write home about, pretty standard with long stop the world phase. It's not super interesting so nobody raves about it. Java's new collectors ZGC and Shenandoah are state of the art, perhaps the best GC's in the world. There's collectors with lower latency and higher throughput but nothing out there that achieves both like Java's new collectors do. It's a large part of why you hear that Java "per…

> Java doesn't have value types and generates a ton of garbage

Yes good point, this is likely a big difference. Maybe Java records will help a lot.

Re: .NET GC Internals mini-series

#67
post #43
post #42

Earlier quoted context omitted.

There's an inherent issue with doing that while still being safe: what if there's still a reference to your "big object" somewhere? The only way for the runtime to know for certain it's safe to delete the object is to effectively run the GC anyway. The alternative is that the object gets deleted without any checks and any references to it will now (probably) cause a crash - and it'll be a hard native crash rather tha…

True but all it takes to fix this is to add if on the reference count of the object. You only need a full scan to handle circular references. You could have a gc.collect(obj) which will collect this object and all of its dependencies provided their reference count has gone to zero. And otherwise do nothing until the next full garbage collection.

There are GCs that take this approach (refcount + sweeps to break cycles). It has tradeoffs however.

- Extra space for every object to have a refcount

- Extra refcount bookkeeping every time you (re)assign a reference (possibly triggering cache thrashing/false sharing in some multithreaded scenarios), xchgs instead of movs, etc.

- Pointless if you're using bump allocators (they can't reuse the 'freed' memory until the next GC cycle compacts memory anyways), so you're forced to use more complicated allocator designs if you're to reuse said memory.

Cycles mean it still doesn't give you 100% deterministic object destruction either, so you want extra mechanisms for disposing unmanaged resources at controlled times ala IDisposable anyways.

Re: .NET GC Internals mini-series

#68
post #33

Earlier quoted context omitted.

Because they're memory objects that are managed by .Net. If you said "Hey, I'm done with this" that's great, but .Net can't actually delete it until it's checked for itself that nothing else is using it. Otherwise you'll inject an error into the memory manager when you delete an object that's still in use. So you can kinda fake a delete by releasing the last reference to an object and then forcing a garbage collectio…

But gc.collect is quite expensive. I think the parent question was, I have a large object in memory, I want to delete it now so that I can load another large object without running into memory limits. But I don't want to call gc.collect which will stop my whole application, interfer with the garbage collector heuristics, and do all sort of unecessay steps. I had instances where I knew only one of these large structur…

If you have large objects which you know you want to deallocate, the easiest way to speed them up is to effectively do manual allocation on top of GC.

When you're done with an object (it will almost certainly be an array), push it onto a stack; when you want to allocate, try and pop it off first before allocating fresh. Use stack per thread or locking as appropriate; use multiple stacks with bucketing by size if there's a lot of variance. Use suballocation to reduce reallocating - e.g. allocate 1MB, 2MB, 4MB and so on arrays and keep track of length separately via a slice (array segment).

(ArrayPool in .net encapsulates most of this for you these days, but it's a thing I implemented myself back in .net 2 days.)

Re: .NET GC Internals mini-series

#69
post #43

Earlier quoted context omitted.

True but all it takes to fix this is to add if on the reference count of the object. You only need a full scan to handle circular references. You could have a gc.collect(obj) which will collect this object and all of its dependencies provided their reference count has gone to zero. And otherwise do nothing until the next full garbage collection.

There's no reference count on the object. That's not how garbage collection works in .NET, or Java for that matter.

They don't define which algorithms are to be used, and at least in what concerns Java, there are some implementations that experimented with reference counting based GCs.

https://www.microsoft.com/en-us/research/wp-content/uploads/...

Re: .NET GC Internals mini-series

#70
post #39

Earlier quoted context omitted.

No that wasn't the problem. You should be able to test it yourself for instance by opening and dereferencing a lot of system.drawing images but not disposing them. You will most likely get an out of memory exception (unless the behavior changed in the last couple of years, and I have not tested it on .net core). Unfortunately the GC doesn't immediatly kick in when you have a memory pressure and you will get an out of…

I want to point out that disposing resources (using the IDisposable) interface does not free memory (unless the memory isn't managed but then it's not related to garbage collection). A System.Drawing.Image holds operating system resources (GDI+) and these resources are released when the Image instance is disposed. If the instance isn't explicitly disposed then the finalizer will do it but this only happens when the g…

Ideally such handles are also wrapped in SafeHandle classes.
Post reply on HN