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?

Reply via email to