Live data from Hacker News

.NET GC Internals mini-series

tooslowexception.com

41–50 of 77 posts

Re: .NET GC Internals mini-series

#41
post #24

Please forgive me ignorance, but why can't I manually delete an object? Lots of times I know this one object is pretty big and want to delete it when I am done with it, but have to force the GC to run to clean it up.

Well, this is tricky.

I'm assuming you're trying to allocate a large array, since otherwise it's pretty difficult to have a large object in .NET.

One thing you can do is use Marshal.AllocHGlobal (this is essentially an unsafe malloc) which gives you a pointer (not reference) to a chunk of unmanaged memory, which you can access with unsafe pointer. This is pretty messy.

The other, more modern thing is using MemoryPool, which gives you a manually managed "array" (Span, .NET-s version of slice) of structs of type T, which you can manually release after you're done with them.

The third option, is just allocate it using new and abandon all references, the create a different copy. The memory pressure of allocating a big object, will probably trigger a GC. This is dangerous, since there can still be dangling references to the old object (that might not even present in the source, but compiler generated), leading you to retain both the old and new memory.

Re: .NET GC Internals mini-series

#42
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…

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 than a .net exception since it's outright accessing invalid memory rather than just a "managed" null pointer.

There's probably something to be said for allowing such a thing in explicitly "unsafe" code, but not in the normal runtime. In fact, it might even be possible now by leveraging some of the existing unsafe bits in .net.

Re: .NET GC Internals mini-series

#43
post #42
post #33

Earlier quoted context omitted.

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…

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.

Re: .NET GC Internals mini-series

#44
post #28
post #26

Earlier quoted context omitted.

With a GC like the one used in .NET, «deleting» an object is a noop. It wouldn’t give you any benefits over not deleting it. You only pay a price for living things (as in, some object holding a reference to it), the rest is free.

I guess I could provide a bit more info. A generational GC as the one in .NET allocates memory up-front, then passes out references/pointers from that allready allocated memory. When the GC is getting close to the end of the pre-allocated memory, it will analyse all living objects (the objects it can reach from the stack and global variables, and objects referenced by those objects) and copies them over to a differen…

> The cost of a generational garbage collector is associated with living objects, not dead ones, so manually deleting doesn’t make sense.

This is true in the context of the discussion, and in general for copying garbage collectors, but it's not always true. Copying is the most common way (and the current .NET way) to implement at least the Eden generation of generational collection, but it could be implemented in other ways.

Re: .NET GC Internals mini-series

#45
post #38
post #20

Earlier quoted context omitted.

> E.g., early versions of the programming language Fortran did not offer dynamic storage allocation, but some programmers would implement their own, say, in a Fortran array. Then for pointers to the allocated storage, just use a subscript on the array name. Even today people do the same thing in languages like Java and Rust as workarounds for performance or semantic constraints of the environment while still nominall…

Because at least array indices are bounds checked. Pointers require hardware support for the same purpose, which Oracle, ARM, Microsoft, Apple and Google are putting money into to, as means to fix C and C++.

Well, without hardware support, you need either a huge overhead ([cached] binary search for a bounding allocation for a pointer every time you use it) or a moderate overhead with fat pointers. It can be done purely in software, but it's not efficient.

Re: .NET GC Internals mini-series

#46
post #6

Earlier quoted context omitted.

Not that this proves anything, but this author's twitter account is followed by: * David Fowler (ASP.NET Core creator) * Andy Gocke (Lead developer for the CLR) * Jared Parsons (Lead developer for C# Compiler team) * Miguel de Icaza Presumably if he wasn't saying anything worth listening to, they would have unfollowed him by now.

* Maoni Stephens (Works on the .NET Garbage Collector ;)

And that is the key fact :). And btw: Thanks David and friends for .NET Core

Re: .NET GC Internals mini-series

#47
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…

Yeah, which is why I mentioned the thing about destructors. The first GC will schedule an object to be disposed/destroyed, but still keep it around for abit.

It’s true that in that case it makes sense to «delete» an object. But in that case you can either use a using block or manually call Dispose, right?

Re: .NET GC Internals mini-series

#48
post #44
post #28

Earlier quoted context omitted.

I guess I could provide a bit more info. A generational GC as the one in .NET allocates memory up-front, then passes out references/pointers from that allready allocated memory. When the GC is getting close to the end of the pre-allocated memory, it will analyse all living objects (the objects it can reach from the stack and global variables, and objects referenced by those objects) and copies them over to a differen…

> The cost of a generational garbage collector is associated with living objects, not dead ones, so manually deleting doesn’t make sense. This is true in the context of the discussion, and in general for copying garbage collectors, but it's not always true. Copying is the most common way (and the current .NET way) to implement at least the Eden generation of generational collection, but it could be implemented in oth…

True. Go, for instance, does not use a generational garbage collector. I also believe that Java’s Zgc is not generational, yet.

But I find it most helpful to instead focus on the current context, otherwise I’d be spending all typing.

Re: .NET GC Internals mini-series

#49
post #47
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…

Yeah, which is why I mentioned the thing about destructors. The first GC will schedule an object to be disposed/destroyed, but still keep it around for abit. It’s true that in that case it makes sense to «delete» an object. But in that case you can either use a using block or manually call Dispose, right?

For an unmanaged object you can use Dispose/using, but for a managed object, I am not aware of any way to explicitly delete it from memory once it has been dereferenced other than calling gc.collect.

For unmanaged object I am surprised it would keep it in memory for a bit since it is also the mean by which you release any lock on a file or a connection. If you don't execute it straight away, you potentially create bugs.

I think the reason why Microsoft was telling people not to call gc.collect is that it interferes with the optimisations and heuristics that the garbage collector maintains to optimise when to do a GC. But I must say I didn't notice any abnormal behaviour when I did. But I would only do if I absolutely have to.

Re: .NET GC Internals mini-series

#50
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…

> I had instances where I knew only one of these large structures could fit in memory and had to call gc.collect before allocating a new one, as I would get an outofmemory exception before the garbage collector would kick in by itself.

That's a GC bug surely?

Post reply on HN