Unittests pass, and then an invalid memory operation happens after?

Liam McGillivray yoshi.pit.link.mario at gmail.com
Thu Mar 28 23:49:19 UTC 2024


On Thursday, 28 March 2024 at 04:46:27 UTC, H. S. Teoh wrote:
> The whole point of a GC is that you leave everything up to it 
> to clean up.  If you want to manage your own memory, don't use 
> the GC. D does not force you to use it; you can import 
> core.stdc.stdlib and use malloc/free to your heart's content.
>
> Unpredictable order of collection is an inherent property of 
> GCs. It's not going away.  If you don't like it, use 
> malloc/free instead. (Or write your own memory management 
> scheme.)

I disagree with this attitude on how the GC should work. Having 
to jump immediately from leaving everything behind for the GC to 
fully manual memory allocation whenever the GC becomes a problem 
is a problem, which gives legitimacy to the common complaint of D 
being "garbage-collected". It would be much better if the garbage 
collector could be there as a backup for when it's needed, while 
allowing the programmer to write code for object destruction when 
they want to optimize.

>> Anyway, I suppose I'll have to experiment with either manually 
>> destroying every object at the end of every unittest, or just 
>> leaving more to the GC. Maybe I'll make a separate `die` 
>> function for the units, if you think it's a good idea.
>
> I think you're approaching this from a totally wrong angle. 
> (Which I sympathize with, having come from a C/C++ background 
> myself.)  The whole point of having a GC is that you *don't* 
> worry about when an object is collected.  You just allocate 
> whatever you need, and let the GC worry about cleaning up after 
> you. The more you let the GC do its job, the better it will be.

Now you're giving me conflicting advice. I was told that my 
current destructor functions aren't acceptable with the garbage 
collector, and you specifically tell me to leave things to the 
GC. But then I suggest that I "leave more to the GC" and move 
everything from the Unit destructor to a specialized `die` 
function that can be called instead of `destroy` whenever they 
must be removed from the game, which as far as I can see is the 
only way to achieve the desired game functionality while 
following your and Steve's advice and not having dangling 
references. But in response to that, you tell me "I think you're 
approaching this from the wrong angle". And then right after 
that, you *again* tell me to "just let the GC worry about 
cleaning up after you"? Even if I didn't call `destroy` at all 
during my program, as far as I can see, I would still need the 
`die` function mentioned to remove a unit on death.

It would be nice if you can clarify your message here. Right now 
I'm confused. I see no way to take your advice without also doing 
the `die` function.

> As far as performance is concerned, a GC actually has higher 
> throughput than manually freeing objects, because in a 
> fragmented heap situation, freeing objects immediately when 
> they go out of use incurs a lot of random access RAM roundtrip 
> costs, whereas a GC that scans memory for references can 
> amortize some of this cost to a single period of time.

By "manually freeing objects", do you mean through `destroy`? If 
so that's actually quite disappointing, as D is often described 
as a "systems programming language", and I thought it would be 
fun to do these optimizations of object destruction, even if I 
have the garbage collector as a backup for anything missed. Or 
did you mean with `malloc` and `free`?

> Now somebody coming from C/C++ would immediately cringe at the 
> thought that a major GC collection might strike at the least 
> opportune time. For that, I'd say:
>
> (1) don't fret about it until it actually becomes a problem. 
> I.e., your program is slow and/or has bad response times, and 
> the profiler is pointing to GC collections as the cause. Then 
> you optimize appropriately with the usual practices for GC 
> optimization: preallocate before your main loop, avoid frequent 
> allocations of small objects (prefer to use structs rather than 
> classes), reuse previous allocations instead of allocating new 
> memory when you know that an existing object is no longer used.

Well, I suppose that's fine for when the GC problem is 
specifically over slowness. I'm quite new to D, so I don't really 
know what it means to "preallocate before your main loop". Is 
this a combination of using `static this` constructors and 
`malloc`? I haven't used `malloc` yet. I have tried making static 
constructors, but every time I've tried them, they caused an 
error to happen immediately after the program is launched.

I've used C++, but I haven't gotten much done with it. It was PHP 
where I made the biggest leap in my programming skills. This game 
is the furthest I've gone at making a complex program from the 
`main` loop up.

I suppose I can turn the `Tile` object into a struct, which I 
suppose will mean replacing all it's references (outside the 
map's `Tile[][] grid`) with pointers. I have thought about this 
before, since tiles are fundamentally associated with one 
particular map, but I chose objects mostly so I can easily pass 
around references to them.

I've already been using structs for the stuff with a short 
lifespan.

> (2) Use D's GC control mechanisms to exercise some control over 
> when collections happen. By default, collections ONLY ever get 
> triggered if you try to allocate something and the heap has run 
> out of memory.  Ergo, if you don't allocate anything, GC 
> collections are guaranteed not to happen.  Use GC.disable and 
> GC.collect to control when collections happen.  In one of my 
> projects, I got a 40% performance boost by using GC.disable and 
> using my own schedule of GC.collect, because the profiler 
> revealed that collections were happening too frequently.  The 
> exact details how what to do will depend on your project, of 
> course, but my point is, there are plenty of tools at your 
> disposal to exercise some degree of control.

> always resort to the nuclear option: slap @nogc on main() and

I want to ask about `@nogc`. Does it simply place restrictions on 
what I can do? Or does it change the meaning of certain lines? 
For example, does it mean that I can still create objects, but 
they will just keep piling up without being cleaned up?


More information about the Digitalmars-d-learn mailing list