www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Unittests pass, and then an invalid memory operation happens after?

reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
In my current [game 
project](https://github.com/LiamM32/Open_Emblem), [something 
strange](https://github.com/LiamM32/Open_Emblem/issues/20) has 
happened as of a recent commit. When running `dub test`, all the 
unittests appear to pass, but then after the last unittest has 
concluded an "Invalid memory operation" happens. Removing a few 
lines replaces this error with a segfault, but either way, these 
errors shouldn't be happening. Weirdly, the commit where these 
errors first appear did nothing but replace a single class-member 
function with a seemingly identical function through a mixin 
template.

The errors seem to be related to object deletion. The traceback 
output for the first error, and where GDB points to for the 
second error, is the destructor for my `Unit` class.

You see, every `Unit` object is associated with a particular 
`Map` object and `Faction` object, which it hold references to. 
Those objects in turn each have an array of `Unit` objects that 
they are associated with. In the `Unit` destructor, I have it 
call the `removeUnit` function in both the associated `Map` and 
`Faction` objects. The errors are happening in either the `Unit` 
destructor itself, or the `removeUnit` function that it calls. 
Until the commit that introduced these errors, the `removeUnit` 
function was written directly in the `Map` class in it's module, 
but this commit replaced it with the mixin template 
`UnitArrayManagement`, which `Faction` also uses.

`Unit` destructor:
```
     ~this() {
         this.alive = false;
         if (this.map !is null) this.map.removeUnit(this);
         if (this.faction !is null) this.faction.removeUnit(this);
         if (this.currentTile !is null) this.currentTile.occupant 
= null;
     }
```

```
template UnitArrayManagement(alias Unit[] unitsArray) {
     bool removeUnit(Unit unit) {
         import std.algorithm.searching;
         writeln("Doing `Map.removeUnit`");
         Unit[] shiftedUnits = unitsArray.find(unit);
         ushort unitKey = cast(ushort)(unitsArray.length - 
shiftedUnits.length);
         debug {
             writeln("unitsArray: ");
             foreach (listedUnit; unitsArray) 
writeln(listedUnit.name~", ");
             writeln("shiftedUnits: ");
             foreach (listedUnit; shiftedUnits) 
writeln(listedUnit.name~", ");
         }
         if (shiftedUnits.length > 0) {
             debug writeln("shiftedUnits.length > 0");
             unitsArray[$-shiftedUnits.length] = null;
             for (ushort i=0; i<shiftedUnits.length-1; i++) {
                 debug writeln("In loop. unitKey = ", unitKey, ", 
i = ", i);
                 unitsArray[unitKey+i] = unitsArray[unitKey+i+1];
             }
             unitsArray.length--;
             return true;
         } else return false;
     }
}
```

The first error happens because I inserted some uses of `writeln` 
for debugging purposes in the new version of `removeUnit` 
(because I haven't figured out how to do the same thing with 
GDB), in which I try to get it to print the names of all the 
units in the array before deleting any of them. I suppose that it 
might get a `Invalid memory operation` when trying to access a 
member of a `Unit` object that no longer exists, but this 
shouldn't be happening. When that other `Unit` object got 
destroyed, the destructor should have called this same 
`removeUnit` function to remove it's reference from the array.

I read that the garbage collector *sometimes* but not *always* 
calls destructors on deletion, which sounds crazy to me. Is this 
a case of one unit being deleted without the destructor and then 
the next unit (of the same `Map` object) having the destructor 
called?

Are destructors normally called after a program is concluded?

The second error, which can be achieved by removing the instances 
of `writeln` in `removeUnit` (making it seemingly identical now 
to the version of this function previously defined in the `Map` 
class) is also strange. It seems to be a case of the `Unit` 
object calling a `Map` object that no longer exists. However, 
that's also strange, as the `Map` object is supposed to delete 
all it's associated units on destruction.

I wrote these destructors so that objects wouldn't have 
references floating around on their deletion, yet now I'm getting 
errors from the destructors.

So why are these things even happening *after* the unittests have 
been run? What else do I need to know about object destruction? 
What may be happening?
Mar 27
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Mar 27, 2024 at 09:43:48PM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
[...]
 ```
     ~this() {
         this.alive = false;
         if (this.map !is null) this.map.removeUnit(this);
         if (this.faction !is null) this.faction.removeUnit(this);
         if (this.currentTile !is null) this.currentTile.occupant = null;
     }
 ```
[...] What's the definition of this.map, this.faction, and this.currentTile? If any of them are class objects, this would be the cause of your problem. Basically, when the dtor runs, there is no guarantee that any referenced classed objects haven't already been collected by the GC. So if you try to access them, it will crash with an invalid memory access. In general, it's a bad idea to do anything that relies on the dtor being run in a particular order, because the GC can collect dead objects in any order. It's also illegal to perform any GC memory-related operations inside a dtor (like allocate memory, free memory, etc.) because the GC is not reentrant. If you need deterministic clean up of your objects, you should do it before the last reference to the object is deleted. T -- He who does not appreciate the beauty of language is not worthy to bemoan its flaws.
Mar 27
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Wednesday, 27 March 2024 at 21:43:48 UTC, Liam McGillivray 
wrote:
 In my current [game 
 project](https://github.com/LiamM32/Open_Emblem), [something 
 strange](https://github.com/LiamM32/Open_Emblem/issues/20) has 
 happened as of a recent commit. When running `dub test`, all 
 the unittests appear to pass, but then after the last unittest 
 has concluded an "Invalid memory operation" happens. Removing a 
 few lines replaces this error with a segfault, but either way, 
 these errors shouldn't be happening. Weirdly, the commit where 
 these errors first appear did nothing but replace a single 
 class-member function with a seemingly identical function 
 through a mixin template.

 The errors seem to be related to object deletion. The traceback 
 output for the first error, and where GDB points to for the 
 second error, is the destructor for my `Unit` class.

 You see, every `Unit` object is associated with a particular 
 `Map` object and `Faction` object, which it hold references to. 
 Those objects in turn each have an array of `Unit` objects that 
 they are associated with. In the `Unit` destructor, I have it 
 call the `removeUnit` function in both the associated `Map` and 
 `Faction` objects. The errors are happening in either the 
 `Unit` destructor itself, or the `removeUnit` function that it 
 calls. Until the commit that introduced these errors, the 
 `removeUnit` function was written directly in the `Map` class 
 in it's module, but this commit replaced it with the mixin 
 template `UnitArrayManagement`, which `Faction` also uses.

 `Unit` destructor:
 ```
     ~this() {
         this.alive = false;
         if (this.map !is null) this.map.removeUnit(this);
         if (this.faction !is null) 
 this.faction.removeUnit(this);
         if (this.currentTile !is null) 
 this.currentTile.occupant = null;
     }
 ```
The GC does not guarantee destructor order. So this code is not valid -- e.g. you can't count on `map` to be a valid object at this point. In my estimation, the code is not correct in principle anyway -- if the `map` has a pointer to the `unit`, then neither will be collected without both being garbage, and so there is no need to do these calls (they are all going away presently). The *only* thing you should be doing in a destructor is freeing non-GC resources.
 I read that the garbage collector *sometimes* but not *always* 
 calls destructors on deletion, which sounds crazy to me.
The GC is not guaranteed to delete memory or run destructors. In the current implementation, it will destroy everything at the end of the program that was allocated using the GC, but the language does not guarantee this.
 The second error, which can be achieved by removing the 
 instances of `writeln` in `removeUnit` (making it seemingly 
 identical now to the version of this function previously 
 defined in the `Map` class) is also strange. It seems to be a 
 case of the `Unit` object calling a `Map` object that no longer 
 exists. However, that's also strange, as the `Map` object is 
 supposed to delete all it's associated units on destruction.
As mentioned, GCs do not work this way -- you do not need to worry about cascading removal of anything. You should assume in the destructor that all references in the type that were pointing at GC blocks are now invalid (i.e. dangling pointers).
 So why are these things even happening *after* the unittests 
 have been run? What else do I need to know about object 
 destruction? What may be happening?
The GC is cleaning up all allocated memory, in *no particular order*. -Steve
Mar 27
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Wednesday, 27 March 2024 at 22:14:16 UTC, H. S. Teoh wrote:
 What's the definition of this.map, this.faction, and 
 this.currentTile?
As was said, and can be found on the linked repository, they are references to class objects. On Thursday, 28 March 2024 at 01:47:27 UTC, Steven Schveighoffer wrote:
 On Wednesday, 27 March 2024 at 21:43:48 UTC, Liam McGillivray 
 wrote:
 `Unit` destructor:
 ```
     ~this() {
         this.alive = false;
         if (this.map !is null) this.map.removeUnit(this);
         if (this.faction !is null) 
 this.faction.removeUnit(this);
         if (this.currentTile !is null) 
 this.currentTile.occupant = null;
     }
 ```
The GC does not guarantee destructor order. So this code is not valid -- e.g. you can't count on `map` to be a valid object at this point.
Well, this was originally done so that when I explicitly call `destroy` on a Unit object (which happens in-game) it will delete all references to itself. Do you think it's better for me to move this code to a `die` function that I can call instead, which will then call for it's own destruction after running those function calls?
 The *only* thing you should be doing in a destructor is freeing 
 non-GC resources.

 I read that the garbage collector *sometimes* but not *always* 
 calls destructors on deletion, which sounds crazy to me.
The GC is not guaranteed to delete memory or run destructors. In the current implementation, it will destroy everything at the end of the program that was allocated using the GC, but the language does not guarantee this.
What's strange is that even if I explicitly call `destroy` for the only two objects at the end of the last unittest, it still happens. I assumed that each unittest was like it's own program, but I suppose not. It's deleting objects from an earlier unittest, right? I may be now starting to see why the use of a garbage collector is such a point of contention for D. Not being able to predict how the garbage collection process will happen seems like a major problem.
 As mentioned, GCs do not work this way -- you do not need to 
 worry about cascading removal of anything.
Wanting to avoid the GC pauses that I hear about, I was trying to optimize object deletion so that the GC doesn't have to look for every object individually. It sounds like what I'm hearing is that I should just leave everything to the GC. While I can do this without really hurting the performance of my program (for now), I don't like this. I hope that solving the unpredictable destruction pattern is a priority for the developers of the language. This problem in my program wouldn't be happening if either *all* of the objects had their destructors called or *none* of them did. 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. Also, check your Github notifications. I have something for you.
Mar 27
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Mar 28, 2024 at 03:56:10AM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
[...]
 I may be now starting to see why the use of a garbage collector is
 such a point of contention for D. Not being able to predict how the
 garbage collection process will happen seems like a major problem.
If you want it to be predictable, simply: import core.memory; GC.disable(); ... // insert your code here if (timeToCleanup()) { GC.collect(); // now you know exactly when this happens } Of course, you'll have to know exactly how timeToCleanup() should decide when it's time to collect. Simple possibilities are once every N units of time, once every N iterations of some main loop, etc.. Or use a profiler to decide.
 As mentioned, GCs do not work this way -- you do not need to worry
 about cascading removal of anything.
Wanting to avoid the GC pauses that I hear about, I was trying to optimize object deletion so that the GC doesn't have to look for every object individually. It sounds like what I'm hearing is that I should just leave everything to the GC. While I can do this without really hurting the performance of my program (for now), I don't like this.
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.
 I hope that solving the unpredictable destruction pattern is a
 priority for the developers of the language. This problem in my
 program wouldn't be happening if either *all* of the objects had their
 destructors called or *none* of them did.
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.)
 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 of course there are situations where you need deterministic destruction, such as freeing up system resources as soon as they're no longer needed (file descriptors, OS shared memory segments allocations, etc.). For these you would manage the memory manually (e.g. with a struct that implements reference counting or whatever is appropriate). 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. 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. In D, you can also selectively allocate certain troublesome objects with malloc/free instead (mixing both types of allocations is perfectly fine in D; we are not Java where you're forced to use the GC no matter what). (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. Or if (1) and (2) are not enough for your particular case, you can always resort to the nuclear option: slap nogc on main() and use malloc/free to your heart's content. IME, however, this is very, very rarely called for. Generally a combination of (1) and (2) has been more than adequate to fix my GC issues. T -- Computerese Irregular Verb Conjugation: I have preferences. You have biases. He/She has prejudices. -- Gene Wirchenko
Mar 27
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
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?
Mar 28
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Mar 28, 2024 at 11:49:19PM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
 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.
Take a look at the docs for core.memory.GC. There *is* a method GC.free that you can use to manually deallocate GC-allocated memory if you so wish. Keep in mind, though, that manually managing memory in this way invites memory-related errors. That's not something I recommend unless you're adamant about doing everything the manual way.
 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.
I think you're conflating two separate concepts, and it would help to distinguish between them. There's the lifetime of a memory-allocated object, which is how long an object remains in the part of the heap that's allocated to it. It begins when you allocate the object with `new`, and ends with the GC finds that it's no longer referenced and collects it. There's a different lifetime that you appear to be talking about: the logical lifetime of an in-game object (not to be confused with an "object" in the OO sense, though the two may overlap). The (game) object gets created (comes into existence in the simulated game world) at a certain point in game time, until something in the game simulation decides that it should no longer exist (it got destroyed, replaced with another object, whatever). At that point, it should be removed from the game simulation, and that's probably also what you have in mind when you mentioned your "die" function. And here's the important point: the two *do not need to coincide*. Here's a concrete example of what I mean. Suppose in your game there's some in-game mechanic that's creating N objects per M turns, and another mechanic that's destroying some of these objects every L turns. If you map these creations/destructions with the object lifetime, you're looking at a *lot* of memory allocations and deallocations throughout the course of your game. Memory allocations and deallocations can be costly; this can become a problem if you're talking about a large number of objects, or if they're being created/destroyed very rapidly (e.g., they are fragments flying out from explosions). Since most of these objects are identical in type, one way of optimizing the code is to preallocate them: before starting your main loop, say you allocate an array of say, 100 objects. Or 1000 or 10000, however many you anticipate you'll need. These objects aren't actually in the game world yet; you're merely reserving the memory for them beforehand. Mark each of them with a "live"-ness flag that indicates whether or not they're actually in the game. Then during your main loop, whenever you need to create a new object of that type, don't allocate memory for it; just find a non-live object in this array, set its fields to the right values, and mark it "live". Now it's a object in the game. When the object is destroyed in-game, don't deallocate it; instead, just set its "live" flag to false. Now you can blast through hundreds and thousands of these objects without incurring the cost of allocating and deallocating them every time. You also save on GC cost (there's nothing for the GC to collect, so it doesn't need to run at all). Don't get confused by the "object" terminology; an in-game object is not necessarily the same thing as a class object in your program. In fact, sometimes it's advantageous to treat them as separate things. [...]
 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`?
By "manually freeing objects" I mean what you typically do when you're using malloc/free. Note that in D, you *can* actually "manually manage" some objects this way by calling GC.free. I don't recommend this, though. It's not how the GC is intended to be used, and it can lead to memory safety problems. My advice remains the same: just let the GC do its job. Don't "optimize" prematurely. Use a profiler to test your program and identify its real bottlenecks before embarking on these often needlessly complicated premature optimizations that may turn out to be completely unnecessary. [...]
 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 gave an example of preallocation above. It's not specific to GC or malloc/free; it's a general principle of reducing the number of allocations inside your main loop. If you can reserve beforehand the memory you know you'll need later, it will generally perform better than if you keep allocating on-the-fly. Allocation and deallocation (or GC collection) always come with a cost; so you want to avoid doing this inside a hot loop, like your main game loop (assuming it's running every frame -- otherwise it's a non-issue and you shouldn't worry about it). Allocating N objects beforehand and then gradually using them as needed, is always better than starting with 0 objects and allocating each one individually inside your hot loop. [...]
 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.
The correct design will depend on how you're using them, so I can't give you a specific recommendation here. If you're conscious of performance, however, I'd say avoid references where you can. Since maps presumably will always exist while the game is going on, why bother with references at all? Just use a struct to store the coordinates of the tile, and look it up in the map. Or if you need to distinguish between tiles belonging to multiple simultaneous maps, then store a reference to the parent map along with the coordinates, then you'll be able to find the right Tile easily. This way your maps can just store an array of Tile structs (single allocation), instead of an array of Tile objects (M*N allocations for an M×N map).
 I've already been using structs for the stuff with a short lifespan.
Good. [...]
 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?
nogc does not change runtime behaviour. It's a static enforcement that prevents your code from doing anything that might trigger GC allocations at all. So using anything that will trigger a GC allocation, such as using `new` or appending to an array with `~`, will cause a compile error. I don't recommend using it (a pretty substantial chunk of the language and stdlib will become unavailable to you), but if you absolutely, totally, 100% want to abstain from using the GC at all, nogc is your ticket to ensure that you didn't accidentally do so. The compiler will enforce it at compile-time. T -- Music critic: "That's an imitation fugue!"
Mar 28
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Friday, 29 March 2024 at 01:18:22 UTC, H. S. Teoh wrote:
 Take a look at the docs for core.memory.GC.  There *is* a 
 method GC.free that you can use to manually deallocate 
 GC-allocated memory if you so wish.  Keep in mind, though, that 
 manually managing memory in this way invites memory-related 
 errors. That's not something I recommend unless you're adamant 
 about doing everything the manual way.
Was this function removed from the library? I don't see it in [the document](https://dlang.org/phobos/core_memory.html). How is `GC.free` different from `destroy`? I might take a look at it, but I'm not adamant about doing everything the manual way, so I probably won't use it very soon.
 I think you're conflating two separate concepts, and it would 
 help to distinguish between them.  There's the lifetime of a 
 memory-allocated object, which is how long an object remains in 
 the part of the heap that's allocated to it.  It begins when 
 you allocate the object with `new`, and ends with the GC finds 
 that it's no longer referenced and collects it.
No. I understand that when an object disappears from memory might happen after it disappears from the game. I'll explain later in this post why I wanted to remove the Unit object from memory when the unit dies.
 There's a different lifetime that you appear to be talking 
 about: the logical lifetime of an in-game object (not to be 
 confused with an "object" in the OO sense, though the two may 
 overlap).  The (game) object gets created (comes into existence 
 in the simulated game world) at a certain point in game time, 
 until something in the game simulation decides that it should 
 no longer exist (it got destroyed, replaced with another 
 object, whatever). At that point, it should be removed from the 
 game simulation, and that's probably also what you have in mind 
 when you mentioned your "die" function.
Yes; exactly. This was your hint that I'm not confusing these two things. Whether or not the unit object gets deleted, I need a function to remove it from the game on death. The `die` function if I want the object to be destroyed on death: ``` void die() { if (this.map !is null) this.map.removeUnit(this); if (this.faction !is null) this.faction.removeUnit(this); if (this.currentTile !is null) this.currentTile.occupant = null; destroy(this); } ``` The `die` function without object destruction: ``` void die() { if (this.map !is null) this.map.removeUnit(this); if (this.faction !is null) this.faction.removeUnit(this); if (this.currentTile !is null) this.currentTile.occupant = null; } ``` They're the same, except that the latter doesn't call `destroy`. The other 3 lines are required to remove references to the object, effectively removing it from the game. With the latter version, I suppose that the garbage collector should eventually clean up the "dead" unit object now that there are no references to it. However, I can see this leading to bugs if there was another reference to the unit which I forgot to remove. One benefit I see in destroying the object when it's no longer needed is that an error will happen if any remaining reference to the object gets accessed, rather than it leading to unexpected behaviour. However I've thought about having it destroy the unit object at the end of the turn rather than immediately. Another option, if I don't want this benefit for debugging but still want fewer deallocations in the end result, would be to set that last line to `version (debug) destroy (this)`. Anyway, I made [a commit](https://github.com/LiamM32/Open_Emblem/commit/64109e556a09ecce73b10 8a9e651744a5e8fcd9) a few days ago that solves the unittest error. I found that explicitly destroying every `Map` object at the end of each unittest that uses it resolved the error. Despite this resolving the error, I decided to also move those lines from the `Unit` destructor to the new `die` function. I currently have it call `destroy` on itself at the end of this new function for the reasons described, but I suppose this line can be removed if I want to.
 And here's the important point: the two *do not need to 
 coincide*. Here's a concrete example of what I mean. Suppose in 
 your game there's some in-game mechanic that's creating N 
 objects per M turns, and another mechanic that's destroying 
 some of these objects every L turns.  If you map these 
 creations/destructions with the object lifetime, you're looking 
 at a *lot* of memory allocations and deallocations throughout 
 the course of your game.  Memory allocations and deallocations 
 can be costly; this can become a problem if you're talking 
 about a large number of objects, or if they're being 
 created/destroyed very rapidly (e.g., they are fragments flying 
 out from explosions).  Since most of these objects are 
 identical in type, one way of optimizing the code is to 
 preallocate them: before starting your main loop, say you 
 allocate an array of say, 100 objects. Or 1000 or 10000, 
 however many you anticipate you'll need. These objects aren't 
 actually in the game world yet; you're merely reserving the 
 memory for them beforehand. Mark each of them with a 
 "live"-ness flag that indicates whether or not they're actually 
 in the game.  Then during your main loop, whenever you need to 
 create a new object of that type, don't allocate memory for it; 
 just find a non-live object in this array, set its fields to 
 the right values, and mark it "live".  Now it's a object in the 
 game.  When the object is destroyed in-game, don't deallocate 
 it; instead, just set its "live" flag to false.  Now you can 
 blast through hundreds and thousands of these objects without 
 incurring the cost of allocating and deallocating them every 
 time.  You also save on GC cost (there's nothing for the GC to 
 collect, so it doesn't need to run at all).
Well, I don't have any explosions or anything fancy like that. Either way, I think all this will run very very fast. The one idea I have that might change this is if I have the enemy AI look multiple turns ahead by cloning all game objects in order to simulate multiple future outcomes. When you mention a "flag" to indicate whether they are "live", do you mean like a boolean member variable for the `Unit` object? Like `bool alive;`?
 My advice remains the same: just let the GC do its job. Don't 
 "optimize" prematurely.  Use a profiler to test your program 
 and identify its real bottlenecks before embarking on these 
 often needlessly complicated premature optimizations that may 
 turn out to be completely unnecessary.
Alright. I suppose that some of the optimization decisions I have made so far may have resulted in less readable code for little performance benefit. Now I'm trying to worry less about optimization. Everything has been very fast so far. I haven't used a profiler yet, but I may like to try it.
 If you're conscious of performance, however, I'd say avoid 
 references where you can.  Since maps presumably will always 
 exist while the game is going on, why bother with references at 
 all?  Just use a struct to store the coordinates of the tile, 
 and look it up in the map.  Or if you need to distinguish 
 between tiles belonging to multiple simultaneous maps, then 
 store a reference to the parent map along with the coordinates, 
 then you'll be able to find the right Tile easily. This way 
 your maps can just store an array of Tile structs (single 
 allocation), instead of an array of Tile objects (M*N 
 allocations for an M×N map).
It's unlikely that I will have multiple maps running simultaneously, unless if I do the AI thing mentioned above. I've had a dilemma of passing around references to the tile object vs passing around the coordinates, as is mentioned in an earlier thread that I started. In what way do references slow down performance? Would passing around a pair of coordinates to functions be better?
Apr 03
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Apr 03, 2024 at 09:57:00PM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
 On Friday, 29 March 2024 at 01:18:22 UTC, H. S. Teoh wrote:
 Take a look at the docs for core.memory.GC.  There *is* a method
 GC.free that you can use to manually deallocate GC-allocated memory
 if you so wish.  Keep in mind, though, that manually managing memory
 in this way invites memory-related errors. That's not something I
 recommend unless you're adamant about doing everything the manual
 way.
Was this function removed from the library? I don't see it in [the document](https://dlang.org/phobos/core_memory.html).
https://dlang.org/phobos/core_memory.html#.GC.free
 How is `GC.free` different from `destroy`?
GC.free is low-level. It does not invoke dtors. [...]
 When you mention a "flag" to indicate whether they are "live", do you
 mean like a boolean member variable for the `Unit` object? Like `bool
 alive;`?
Yes.
 My advice remains the same: just let the GC do its job. Don't
 "optimize" prematurely.  Use a profiler to test your program and
 identify its real bottlenecks before embarking on these often
 needlessly complicated premature optimizations that may turn out to
 be completely unnecessary.
Alright. I suppose that some of the optimization decisions I have made so far may have resulted in less readable code for little performance benefit. Now I'm trying to worry less about optimization. Everything has been very fast so far. I haven't used a profiler yet, but I may like to try it.
Never make any optimization decisions without a profiler. I learned the hard way that more often than not, I'm wrong about where my program's bottleneck is, and that I spend far too much time and effort "optimizing" things that don't need to be optimized, while totally missing optimizations where it really matters. Life is too short to be wasted on optimizing things that don't really matter. When it comes to optimizations, always profile, profile, profile! [...]
 It's unlikely that I will have multiple maps running simultaneously,
 unless if I do the AI thing mentioned above. I've had a dilemma of
 passing around references to the tile object vs passing around the
 coordinates, as is mentioned in an earlier thread that I started. In
 what way do references slow down performance? Would passing around a
 pair of coordinates to functions be better?
It's not references themselves that slow things down; it's the likelihood that using reference types when you don't need to can lead to excessive GC allocations, which in turn causes longer GC pauses. Well, excessive dereferencing can also reduce cache coherence, but if you're already at the level where this actually makes a difference, you don't my advice anymore. :-D Generally, if a piece of data is transient and not expected to last very long (e.g., past the current frame), it probably should be a struct rather than a class. There are exceptions, of course, but generally that's how I'd decide whether something should be a by-value type or a by-reference type. T -- "The number you have dialed is imaginary. Please rotate your phone 90 degrees and try again."
Apr 06
prev sibling parent Lance Bachmeier <no spam.net> writes:
On Wednesday, 3 April 2024 at 21:57:00 UTC, Liam McGillivray 
wrote:

 Alright. I suppose that some of the optimization decisions I 
 have made so far may have resulted in less readable code for 
 little performance benefit. Now I'm trying to worry less about 
 optimization. Everything has been very fast so far.

 I haven't used a profiler yet, but I may like to try it.
A good example where you people will get fooled is to avoid passing structs as function arguments/return values because you're worried about copying: https://forum.dlang.org/post/jifumskhdmxkimtayzva forum.dlang.org I'm guilty of having done that regularly until I learned how the compiler actually works and how little time certain operations take. One time I even changed clear string arguments to hard-to-remember single char values inside a loop.
Apr 06