www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Memory management dilemma

reply Jeff Parsons <jeffrparsons optusnet.com.au> writes:
Hi

Straight to the point: I'm having trouble finding a clean way to make 
sure memory that's no longer required by my game is freed.

I need to manage memory explicitly since it seems to be impossible to 
guarantee long-term smooth operation when using only garbage collection. 
I don't see introducing artificial pauses into gameplay during which 
garbage is collected, or allowing the game to bloat horribly over time 
as solutions.

Example of my problem:

In C++, I could pass my spatial Vector/Vector3D/whatever class, or 
Matrix class by value. I could chuck it around in expressions happily, 
etc. In D, I have a few concerns:

[*] I can see a LOT of manual "delete whatever" popping up if I'm 
dealing with expressions that involve a lot of vector or matrix math. Is 
there a way to avoid this (having locally created objects automatically 
destroyed when they go out of scope)?
[*] If I explicitly copy my objects when passing them to functions and 
explicitly destroy them at the end of the called function, won't this be 
allocating a lot on the heap, and therefore be slow?
[*] How are temporaries handled if there's no garbage collector active? 
For example, if part of an expression calls a.getB() which returns a 
reference to one of a's members called b, does D know not to mess with 
this reference when cleaning up temporaries? What if getB() assigned a 
new object to b, but returned the original reference. How would it 
behave then - would it know it's not safe to destroy it?

Is there a nice neat way to handle this in D or should I expect a lot of 
pain when not using the garbage collector? And if I do go down this 
path, how can I tell if I -am- leaking memory?

Any and all comments and/or suggestions are appreciated.

Thanks :)

---------

Footer for the bored:
I don't think I've posted here, so to quickly introduce myself, my name 
is Jeff Parsons and I'm an undergraduate Computer Science student who 
really doesn't want to go crawling back to C++ quite so soon. ;)
May 28 2006
next sibling parent reply mclysenk mtu.edu writes:
In article <e5c89d$6ki$2 digitaldaemon.com>, Jeff Parsons says...
Hi

Straight to the point: I'm having trouble finding a clean way to make 
sure memory that's no longer required by my game is freed.

I need to manage memory explicitly since it seems to be impossible to 
guarantee long-term smooth operation when using only garbage collection. 
I don't see introducing artificial pauses into gameplay during which 
garbage is collected, or allowing the game to bloat horribly over time 
as solutions.

I thought about this for a lot as well and here's what I came up with: Use structs. In D structs are copy on write, and therefore don't need memory management. This makes them ideal for simple mathematical primitives, but a terrible idea for larger objects. Classes on the other hand are pass by reference, and are allocated only in heap space. If you would like to see an example, I have a vector/matrix library for Derelict/OpenGL that I've written. I can post the source if anyone is interested. -Mik
May 28 2006
parent reply Jeff Parsons <jeffrparsons optusnet.com.au> writes:
 I thought about this for a lot as well and here's what I came up with:
 
 Use structs.
 
 In D structs are copy on write, and therefore don't need memory management.
 This makes them ideal for simple mathematical primitives, but a terrible idea
 for larger objects. Classes on the other hand are pass by reference, and are
 allocated only in heap space.
 
 If you would like to see an example, I have a vector/matrix library for
 Derelict/OpenGL that I've written.  I can post the source if anyone is
 interested.
 
 -Mik

Thanks! And sure, I'd like to see your implementation if it's not too much trouble. :) -Jeff
May 29 2006
parent reply mclysenk mtu.edu writes:
In article <e5ercl$2v9i$1 digitaldaemon.com>, Jeff Parsons says...
Thanks!

And sure, I'd like to see your implementation if it's not too much 
trouble. :)

Ok! Here you can get it from my brand new website: http://www.assertfalse.com/downloads/vecmat-0.1.zip -Mik
May 29 2006
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
mclysenk mtu.edu wrote:
 In article <e5ercl$2v9i$1 digitaldaemon.com>, Jeff Parsons says...
 Thanks!

 And sure, I'd like to see your implementation if it's not too much 
 trouble. :)

Ok! Here you can get it from my brand new website: http://www.assertfalse.com/downloads/vecmat-0.1.zip -Mik

I love the domain name. Pity you can't have parenthesis in domain names, eh? I only ever came up with one good domain name/website name... then I made the mistake of telling someone else :P -- Daniel -- v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 29 2006
parent reply James Dunne <james.jdunne gmail.com> writes:
Daniel Keep wrote:
 
 mclysenk mtu.edu wrote:
 
In article <e5ercl$2v9i$1 digitaldaemon.com>, Jeff Parsons says...

Thanks!

And sure, I'd like to see your implementation if it's not too much 
trouble. :)

Ok! Here you can get it from my brand new website: http://www.assertfalse.com/downloads/vecmat-0.1.zip -Mik

I love the domain name. Pity you can't have parenthesis in domain names, eh? I only ever came up with one good domain name/website name... then I made the mistake of telling someone else :P -- Daniel

Really? What was it? =P -- Regards, James Dunne
May 29 2006
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
James Dunne wrote:
 Daniel Keep wrote:
 mclysenk mtu.edu wrote:

 In article <e5ercl$2v9i$1 digitaldaemon.com>, Jeff Parsons says...

 Thanks!

 And sure, I'd like to see your implementation if it's not too much
 trouble. :)

Ok! Here you can get it from my brand new website: http://www.assertfalse.com/downloads/vecmat-0.1.zip -Mik

I love the domain name. Pity you can't have parenthesis in domain names, eh? I only ever came up with one good domain name/website name... then I made the mistake of telling someone else :P -- Daniel

Really? What was it? =P

Nooo! There's still hope; it was registered as a subdomain. I can still get it... yessss my precious... -- Daniel -- v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 29 2006
parent Jeff Parsons <jeffrparsons optusnet.com.au> writes:
Daniel Keep wrote:
 
 James Dunne wrote:
 Daniel Keep wrote:
 mclysenk mtu.edu wrote:

 In article <e5ercl$2v9i$1 digitaldaemon.com>, Jeff Parsons says...

 Thanks!

 And sure, I'd like to see your implementation if it's not too much
 trouble. :)

http://www.assertfalse.com/downloads/vecmat-0.1.zip -Mik

I love the domain name. Pity you can't have parenthesis in domain names, eh? I only ever came up with one good domain name/website name... then I made the mistake of telling someone else :P -- Daniel


Nooo! There's still hope; it was registered as a subdomain. I can still get it... yessss my precious... -- Daniel

I have dividezero.com; not actually used for anything useful yet, but I'm working on content right now. Check back soon. ;)
May 29 2006
prev sibling next sibling parent reply Rémy Mouëza <Rémy_member pathlink.com> writes:
In article <e5c89d$6ki$2 digitaldaemon.com>, Jeff Parsons says...
I need to manage memory explicitly since it seems to be impossible to 
guarantee long-term smooth operation when using only garbage collection. 
I don't see introducing artificial pauses into gameplay during which 
garbage is collected, or allowing the game to bloat horribly over time 
as solutions.

I discussed about similar concerns with a friend of mine: is a C programmer and think nothing is better than C. He discovered that D is binary compatible with C and we're planning to collaborate on a game engine. Memory management was (and might still be) the first problem we needed to solve.
[*] I can see a LOT of manual "delete whatever" popping up if I'm 
dealing with expressions that involve a lot of vector or matrix math. Is 
there a way to avoid this (having locally created objects automatically 
destroyed when they go out of scope)?

Yes, there is a way: auto variable. # Result aFunction () # { # auto Matrix matrix = new Matrix ; # // ... work with your matrix # return result ; # } # // getting out of scope deletes # // the auto allocated matrix. You can also make an auto class : auto class Matrix { ... } All the instances of this class will be deleted when getting out of scope. There are restriction in the use of auto variable : you can't return an auto variable and... I've forgotten, they're due to the temporary nature of such instances. See: http://www.digitalmars.com/d/declaration.html#AutoDeclaration http://www.digitalmars.com/d/class.html#deallocators, the section after deallocators deals with auto classes. There is also the scope ( exit ) {} statement that defer the execution of its instructions at the exit of the current scope. http://www.digitalmars.com/d/statement.html#scope Less convenient than the auto variable.
Is there a nice neat way to handle this in D or should I expect a lot of 
pain when not using the garbage collector? And if I do go down this 
path, how can I tell if I -am- leaking memory?

allocations. I don't know if it works for D.
May 28 2006
parent Jeff Parsons <jeffrparsons optusnet.com.au> writes:
Rémy Mouëza wrote:
 In article <e5c89d$6ki$2 digitaldaemon.com>, Jeff Parsons says...
 I need to manage memory explicitly since it seems to be impossible to 
 guarantee long-term smooth operation when using only garbage collection. 
 I don't see introducing artificial pauses into gameplay during which 
 garbage is collected, or allowing the game to bloat horribly over time 
 as solutions.

I discussed about similar concerns with a friend of mine: is a C programmer and think nothing is better than C. He discovered that D is binary compatible with C and we're planning to collaborate on a game engine. Memory management was (and might still be) the first problem we needed to solve.
 [*] I can see a LOT of manual "delete whatever" popping up if I'm 
 dealing with expressions that involve a lot of vector or matrix math. Is 
 there a way to avoid this (having locally created objects automatically 
 destroyed when they go out of scope)?

Yes, there is a way: auto variable. # Result aFunction () # { # auto Matrix matrix = new Matrix ; # // ... work with your matrix # return result ; # } # // getting out of scope deletes # // the auto allocated matrix. You can also make an auto class : auto class Matrix { ... } All the instances of this class will be deleted when getting out of scope. There are restriction in the use of auto variable : you can't return an auto variable and... I've forgotten, they're due to the temporary nature of such instances. See: http://www.digitalmars.com/d/declaration.html#AutoDeclaration http://www.digitalmars.com/d/class.html#deallocators, the section after deallocators deals with auto classes. There is also the scope ( exit ) {} statement that defer the execution of its instructions at the exit of the current scope. http://www.digitalmars.com/d/statement.html#scope Less convenient than the auto variable.
 Is there a nice neat way to handle this in D or should I expect a lot of 
 pain when not using the garbage collector? And if I do go down this 
 path, how can I tell if I -am- leaking memory?

allocations. I don't know if it works for D.

Thanks for the advice. I'll definitely look into Valgrind, and make use of auto where appropriate.
May 29 2006
prev sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Jeff Parsons wrote:
 Hi
 

Hi. My computer just blue-screened while replying to this, so I hope you appreciate me typing all this out again :P
 Straight to the point: I'm having trouble finding a clean way to make
 sure memory that's no longer required by my game is freed.
 
 I need to manage memory explicitly since it seems to be impossible to
 guarantee long-term smooth operation when using only garbage collection.
 I don't see introducing artificial pauses into gameplay during which
 garbage is collected, or allowing the game to bloat horribly over time
 as solutions.
 
 Example of my problem:
 
 In C++, I could pass my spatial Vector/Vector3D/whatever class, or
 Matrix class by value. I could chuck it around in expressions happily,
 etc. In D, I have a few concerns:
 

If you want to pass stuff by-value, you can use a struct. Catch: no constructors or destructors. But, you can do this instead: # struct Vector3D # { # double x, y, z; # # static Vector3D opCall(double x, double y, double z) # { # Vector3D result; # result.x = x; result.y = y; result.z = y; # return result; # } # } # # Vector3D a = Vector3D(1,2,3);
 [*] I can see a LOT of manual "delete whatever" popping up if I'm
 dealing with expressions that involve a lot of vector or matrix math. Is
 there a way to avoid this (having locally created objects automatically
 destroyed when they go out of scope)?

Three main ways, actually: 1. Manually 'deleting' stuff: no fun at all. 2. Using 'auto'. Any object variable marked with 'auto' will automatically destroy itself at the end of it's scope. Example: # { # // ... # # auto Foo bar = new Foo; # # // ... # # { # # auto Foo baz = new Foo; # # } // baz is destroyed here # # // ... # # } // bar is destroyed here One thing to watch out for: Auto-deleted variable: # auto Foo bar = new Foo; Not auto-deleted, with type inference: # auto bar = new Foo; 'auto' without a type name after it means that D will work out the type of the variable itself. Just watch out for it :) If you want to make sure instances of a particular class are always 'auto', you can put the 'auto' keyword out the front of the class declaration: # auto class Foo # { # // ... # } # # // MUST be declared auto: # auto Foo a; # Foo a; // <-- this won't compile However, keep in mind that 'auto' objects can't be put into 'out' or 'inout' function arguments, you can't have global 'auto' variables, you can't have 'auto' members, and you can't return them out of functions [1]. 3. Using 'scope(exit)'. You use 'scope(exit)' like a control statement: any statement (or block of statements) you put after a 'scope(exit)' will be executed when control leaves that scope. For example: # { # # // ... # # Foo bar = new Foo; # scope(exit) delete bar; # # // ... # # } // 'delete bar' is executed here Please note that 'scope(exit)' blocks will execute irrespective of HOW you leave the scope: a return statement, break statement, goto or an exception. There's also 'scope(fail)' which only executes if you leave the scope via an exception, and 'scope(success)' for when you do NOT leave because of an exception :) As for dealing with temporaries (which I know I haven't actually answered :P), then... we'll I don't know. Unless you keep a reference to each temporary object in an expression, I suppose you'd have to leave the GC to handle that. That or, again, use structs.
 [*] If I explicitly copy my objects when passing them to functions and
 explicitly destroy them at the end of the called function, won't this be
 allocating a lot on the heap, and therefore be slow?

I don't know for sure, but I would suppose so. If you have a need to do this, you can try making caches of pre-allocated objects, and then reusing them. See http://digitalmars.com/d/memory.html for some ideas.
 [*] How are temporaries handled if there's no garbage collector active?
 For example, if part of an expression calls a.getB() which returns a
 reference to one of a's members called b, does D know not to mess with
 this reference when cleaning up temporaries? What if getB() assigned a
 new object to b, but returned the original reference. How would it
 behave then - would it know it's not safe to destroy it?

I'm assuming you mean using 'std.gc.disable()' when you say 'no gc active'. In that case, nothing is collected. If you run out of memory, then you get an OutOfMemory exception. If you don't already know: the D gc NEVER collects objects until you run out of memory. That's when it does a collection. One suggestion is to make a very low-priority thread that runs in the background, calling 'std.gc.fullCollect()' over and over again to keep your memory profile from growing too large. One idea I once had was to keep an eye on the amount of memory your game is using. At start up, compute (MaxMemory - CurrentlyUsedMemory). If you're using between 0% - 20% of that, don't bother collecting. If you're using 20% - 40%, run a very-low priority background collect. If you get to 40% - 60%, run a higher-priority collect, etc. That way, the time spent running collections is proportional to how much memory you're using. The cost of running the GC is amortized over time, instead of being in one great big lump [2].
 Is there a nice neat way to handle this in D or should I expect a lot of
 pain when not using the garbage collector? And if I do go down this
 path, how can I tell if I -am- leaking memory?
 
 Any and all comments and/or suggestions are appreciated.
 
 Thanks :)

I don't think you could get away with *not* using the GC. I imagine that most library code uses it at least a little. I think the idea would be to minimize your own use of it, and keep a tight handle on your own memory. I've thought about writing my own game engine in D before, and how I would manage my memory. Aside from using structs for very small, and/or short-lived objects, there's using scoped destruction. If you need to hold on to something for a bit longer, and you want it destroyed the *second* you no longer need it, you've got a few choices: * You can write a reference counter template that wraps objects. When you duplicate the reference, it adds one to it's count. When the count drops to zero, it kills the object it's wrapping. If you make judicious use of 'auto' and references, then you've got a rudimentary reference counting system [3]. * Another strategy is to use object managers; you hand references to the manager. When the manager gets 'delete'd, it deletes all the references it holds on to. You can use this for things like texture caches: when you load a new level, create a texture cache. When you change levels, or leave the game, just kill the level's texture cache. Anyway, I hope this has helped, or at least given you some ideas. Since I'm still learning D myself, take all of the above with a grain or two of salt.
 
 ---------
 
 Footer for the bored:
 I don't think I've posted here, so to quickly introduce myself, my name
 is Jeff Parsons and I'm an undergraduate Computer Science student who
 really doesn't want to go crawling back to C++ quite so soon. ;)

Ditto :) Well, except for the "Jeff Parsons" bit... -- Daniel [1] I proposed to have those last two restrictions relaxed a while back, but I think maybe one person in total read it :( Oh well. [2] Believe you me: "all at once" GCs are horrible for games. World of Warcraft uses Lua, which has a mark and sweep GC. The problem with this was that until I stuck an extra half gig of RAM in my system, every time Lua had to do a GC it would take anywhere up to TWENTY SECONDS to complete, during which the game was totally unresponsive. I died *so many times* because of that... [3] The only catch with this approach is that you need to make sure you *always* use 'auto'; otherwise you can leak references. -- v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 28 2006
parent reply Jeff Parsons <jeffrparsons optusnet.com.au> writes:
Daniel Keep wrote:

 
 Hi.  My computer just blue-screened while replying to this, so I hope
 you appreciate me typing all this out again :P
 

I certainly do; this has been really damn helpful. :)
 
 If you want to pass stuff by-value, you can use a struct.
 
 Catch: no constructors or destructors.  But, you can do this instead:
 
 # struct Vector3D
 # {
 #     double x, y, z;
 #
 #     static Vector3D opCall(double x, double y, double z)
 #     {
 #         Vector3D result;
 #         result.x = x; result.y = y; result.z = y;
 #         return result;
 #     }
 # }
 #
 # Vector3D a = Vector3D(1,2,3);
 

You've won me over. This certainly solves the "millions of temporaries being created" issue, for one!
 
 Three main ways, actually:
 
 1. Manually 'deleting' stuff: no fun at all.

Would it still be useful to do this sometimes in deconstructors if I know a few members that can safely be cleaned up immediately, so the garbage collector doesn't have to sift through things later? Or isn't this likely to give me any advantage at all?
 2. Using 'auto'.  Any object variable marked with 'auto' will
 automatically destroy itself at the end of it's scope.  Example:
 
 # {
 #     // ...
 #
 #     auto Foo bar = new Foo;
 #
 #     // ...
 #
 #     {
 #
 #         auto Foo baz = new Foo;
 #
 #     } // baz is destroyed here
 #
 #     // ...
 #
 # } // bar is destroyed here
 
 One thing to watch out for:
 
 Auto-deleted variable:
 #	auto Foo bar = new Foo;
 
 Not auto-deleted, with type inference:
 #	auto bar = new Foo;
 
 'auto' without a type name after it means that D will work out the type
 of the variable itself.  Just watch out for it :)
 
 If you want to make sure instances of a particular class are always
 'auto', you can put the 'auto' keyword out the front of the class
 declaration:
 
 # auto class Foo
 # {
 #     // ...
 # }
 #
 # // MUST be declared auto:
 # auto Foo a;
 # Foo a; // <-- this won't compile
 
 However, keep in mind that 'auto' objects can't be put into 'out' or
 'inout' function arguments, you can't have global 'auto' variables, you
 can't have 'auto' members, and you can't return them out of functions [1].
 
 3. Using 'scope(exit)'.  You use 'scope(exit)' like a control statement:
 any statement (or block of statements) you put after a 'scope(exit)'
 will be executed when control leaves that scope.  For example:
 
 # {
 #
 #     // ...
 #
 #     Foo bar = new Foo;
 #     scope(exit) delete bar;
 #
 #     // ...
 #
 # } // 'delete bar' is executed here
 
 Please note that 'scope(exit)' blocks will execute irrespective of HOW
 you leave the scope: a return statement, break statement, goto or an
 exception.
 
 There's also 'scope(fail)' which only executes if you leave the scope
 via an exception, and 'scope(success)' for when you do NOT leave because
 of an exception :)

Noted and understood; I'll certainly be making much use of -all- of that! :)
 As for dealing with temporaries (which I know I haven't actually
 answered :P), then... we'll I don't know.  Unless you keep a reference
 to each temporary object in an expression, I suppose you'd have to leave
 the GC to handle that.
 
 That or, again, use structs.

Yeah, I think I'd be going for structs there. For one, I don't see it being an issue expect in cases like vectors and matrices where they're likely to be involved in a lot of number crunching.
 I'm assuming you mean using 'std.gc.disable()' when you say 'no gc active'.
 
 In that case, nothing is collected.  If you run out of memory, then you
 get an OutOfMemory exception.

I wasn't actually, because I was under the (now seemingly misguided) impression that the garbage collector wasn't initialized by default. And also the impression that it would be practical attempting to live without it. :P
 If you don't already know: the D gc NEVER collects objects until you run
 out of memory.  That's when it does a collection.  One suggestion is to
 make a very low-priority thread that runs in the background, calling
 'std.gc.fullCollect()' over and over again to keep your memory profile
 from growing too large.

Ok. I'd interpreted "It is not predictable when a collection gets run, so the program can arbitrarily pause" (from http://www.digitalmars.com/d/garbage.html) as meaning that it could just 'decide' to kick in upon any operation that allocates memory, even if you weren't running low. From the same page: "All threads other than the collector thread must be halted while the collection is in progress." That seems a little worrying. Wouldn't this mean that running it in a low-priority thread wouldn't actually achieve much for a game? i.e. if the GC decides it wants to do some huge cleanup, then there's still no way to have it broken down, since the GC thread won't relinquish control until it's done? Do you know if generational garbage collection using genCollect() would help, or is this just as likely to be arbitrarily 'greedy' for time? If not, is there an ideal solution for incremental garbage collection that could be built into D, or would this kill the GC's effectiveness completely?
 One idea I once had was to keep an eye on the amount of memory your game
 is using.  At start up, compute (MaxMemory - CurrentlyUsedMemory).  If
 you're using between 0% - 20% of that, don't bother collecting.  If
 you're using 20% - 40%, run a very-low priority background collect.  If
 you get to 40% - 60%, run a higher-priority collect, etc.
 
 That way, the time spent running collections is proportional to how much
 memory you're using.  The cost of running the GC is amortized over time,
 instead of being in one great big lump [2].

Ok, re-thinking my above comment: does this mean that by continually forcing a full collection in a low-priority thread I could guarantee that individual garbage collections aren't going to take long? If so, wouldn't it then be feasible to force it constantly (every frame of the game) in my main thread anyway? [noted, snipped]
 
 Anyway, I hope this has helped, or at least given you some ideas.  Since
 I'm still learning D myself, take all of the above with a grain or two
 of salt.
 

Ok, I'll keep that in mind. I've even had the audacity to question some of your advice above! ;) Thanks for all your help; I appreciate your time, and sympathize with your blue-screen nightmares! :P
May 29 2006
next sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Jeff Parsons wrote:
 Daniel Keep wrote:
 
 Hi.  My computer just blue-screened while replying to this, so I hope
 you appreciate me typing all this out again :P

I certainly do; this has been really damn helpful. :)
 If you want to pass stuff by-value, you can use a struct.

 Catch: no constructors or destructors.  But, you can do this instead:

 # struct Vector3D
 # {
 #     double x, y, z;
 #
 #     static Vector3D opCall(double x, double y, double z)
 #     {
 #         Vector3D result;
 #         result.x = x; result.y = y; result.z = y;
 #         return result;
 #     }
 # }
 #
 # Vector3D a = Vector3D(1,2,3);

You've won me over. This certainly solves the "millions of temporaries being created" issue, for one!
 Three main ways, actually:

 1. Manually 'deleting' stuff: no fun at all.

Would it still be useful to do this sometimes in deconstructors if I know a few members that can safely be cleaned up immediately, so the garbage collector doesn't have to sift through things later? Or isn't this likely to give me any advantage at all?

It gives you an advantage if you *want* them cleaned up in a timely manner. If you just don't care, then you can leave them for the GC. There's a big catch to this, though: the GC will destroy objects in any old fashion; it's non-deterministic. The problem with this is: # class Foo # { # private Bar qux; // Bar is a class as well # # // ... # # ~this() # { # delete qux; # } # } That delete may fail! Because you can't tell what order the GC cleans stuff up, it's entirely possible that by the time Foo's destructor is called, 'qux' may have already been destroyed. HOWEVER, if you manually delete an object, or use 'auto', then you know the exact order in which objects are destroyed, and there's no problem. So the rule of thumb is: if a class is designed to be managed by the GC, don't try to do anything to member objects in the destructor (doing things like, say, freeing GL textures is fine since the GC doesn't manage those). On the other hand, if you always 'auto' or manually delete instances of a class, manually deleting member objects is fine. On a side note, unless I'm mistaken, the Ares project (which is an alternate standard library to Phobos) has support for passing an arg into destructors to indicate if they've been collected by the GC or explicitly deleted.
 2. Using 'auto'.  Any object variable marked with 'auto' will
 automatically destroy itself at the end of it's scope.  Example:

 # {
 #     // ...
 #
 #     auto Foo bar = new Foo;
 #
 #     // ...
 #
 #     {
 #
 #         auto Foo baz = new Foo;
 #
 #     } // baz is destroyed here
 #
 #     // ...
 #
 # } // bar is destroyed here

 One thing to watch out for:

 Auto-deleted variable:
 #    auto Foo bar = new Foo;

 Not auto-deleted, with type inference:
 #    auto bar = new Foo;

 'auto' without a type name after it means that D will work out the type
 of the variable itself.  Just watch out for it :)

 If you want to make sure instances of a particular class are always
 'auto', you can put the 'auto' keyword out the front of the class
 declaration:

 # auto class Foo
 # {
 #     // ...
 # }
 #
 # // MUST be declared auto:
 # auto Foo a;
 # Foo a; // <-- this won't compile

 However, keep in mind that 'auto' objects can't be put into 'out' or
 'inout' function arguments, you can't have global 'auto' variables, you
 can't have 'auto' members, and you can't return them out of functions
 [1].

 3. Using 'scope(exit)'.  You use 'scope(exit)' like a control statement:
 any statement (or block of statements) you put after a 'scope(exit)'
 will be executed when control leaves that scope.  For example:

 # {
 #
 #     // ...
 #
 #     Foo bar = new Foo;
 #     scope(exit) delete bar;
 #
 #     // ...
 #
 # } // 'delete bar' is executed here

 Please note that 'scope(exit)' blocks will execute irrespective of HOW
 you leave the scope: a return statement, break statement, goto or an
 exception.

 There's also 'scope(fail)' which only executes if you leave the scope
 via an exception, and 'scope(success)' for when you do NOT leave because
 of an exception :)

Noted and understood; I'll certainly be making much use of -all- of that! :)
 As for dealing with temporaries (which I know I haven't actually
 answered :P), then... we'll I don't know.  Unless you keep a reference
 to each temporary object in an expression, I suppose you'd have to leave
 the GC to handle that.

 That or, again, use structs.

Yeah, I think I'd be going for structs there. For one, I don't see it being an issue expect in cases like vectors and matrices where they're likely to be involved in a lot of number crunching.
 I'm assuming you mean using 'std.gc.disable()' when you say 'no gc
 active'.

 In that case, nothing is collected.  If you run out of memory, then you
 get an OutOfMemory exception.

I wasn't actually, because I was under the (now seemingly misguided) impression that the garbage collector wasn't initialized by default. And also the impression that it would be practical attempting to live without it. :P

Well, I'm not saying it's impossible; I've never tried to disable the GC. I've seen a few modified versions of Phobos lying around that completely omit the GC. Perhaps a compromise would be to see if you can live without it in *your* code, and then if you need the performance, create a specialized version of the standard library. Also, I'm only ~30% sure on this, but I think Ares (as mentioned above) avoids using the GC. That might be worth taking a gander at.
 If you don't already know: the D gc NEVER collects objects until you run
 out of memory.  That's when it does a collection.  One suggestion is to
 make a very low-priority thread that runs in the background, calling
 'std.gc.fullCollect()' over and over again to keep your memory profile
 from growing too large.

Ok. I'd interpreted "It is not predictable when a collection gets run, so the program can arbitrarily pause" (from http://www.digitalmars.com/d/garbage.html) as meaning that it could just 'decide' to kick in upon any operation that allocates memory, even if you weren't running low. From the same page: "All threads other than the collector thread must be halted while the collection is in progress." That seems a little worrying. Wouldn't this mean that running it in a low-priority thread wouldn't actually achieve much for a game? i.e. if the GC decides it wants to do some huge cleanup, then there's still no way to have it broken down, since the GC thread won't relinquish control until it's done?

Hmm. Funny, because I got the "low-priority thread" idea from http://digitalmars.com/d/phobos/std_gc.html (Checks) Aah, I had missed this little tidbit: "...so that if the program is idly waiting for input, memory can be cleaned up." Bugger. I wonder if it's possible to write a thread-safe collector :P
 Do you know if generational garbage collection using genCollect() would
 help, or is this just as likely to be arbitrarily 'greedy' for time? If
 not, is there an ideal solution for incremental garbage collection that
 could be built into D, or would this kill the GC's effectiveness
 completely?

If I remember correctly, genCollect() isn't implemented yet. That, or it's the same as calling fullCollect().
 
 One idea I once had was to keep an eye on the amount of memory your game
 is using.  At start up, compute (MaxMemory - CurrentlyUsedMemory).  If
 you're using between 0% - 20% of that, don't bother collecting.  If
 you're using 20% - 40%, run a very-low priority background collect.  If
 you get to 40% - 60%, run a higher-priority collect, etc.

 That way, the time spent running collections is proportional to how much
 memory you're using.  The cost of running the GC is amortized over time,
 instead of being in one great big lump [2].

Ok, re-thinking my above comment: does this mean that by continually forcing a full collection in a low-priority thread I could guarantee that individual garbage collections aren't going to take long? If so, wouldn't it then be feasible to force it constantly (every frame of the game) in my main thread anyway? [noted, snipped]

I'm guessing from how the sentence was phrased that the GC isn't thread-safe. When you think about it, it really couldn't be; otherwise other running threads could screw up the collect. What would be worth doing is maybe running some benchmarks to see how memory usage/amount of garbage affects the length of collects. If it's low enough, then maybe running a quick collect between every few frames might work... dunno tho. Accursed real-world facts! They always have to ruin good ideas :P
 Anyway, I hope this has helped, or at least given you some ideas.  Since
 I'm still learning D myself, take all of the above with a grain or two
 of salt.

Ok, I'll keep that in mind. I've even had the audacity to question some of your advice above! ;) Thanks for all your help; I appreciate your time, and sympathize with your blue-screen nightmares! :P

Well, hope I didn't confuse you too much with the threaded collects thing. Like I said, grain of salt :) -- Daniel -- v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 29 2006
prev sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Jeff Parsons wrote:
 Daniel Keep wrote:
 
 Hi.  My computer just blue-screened while replying to this, so I hope
 you appreciate me typing all this out again :P

I certainly do; this has been really damn helpful. :)
 If you want to pass stuff by-value, you can use a struct.

 Catch: no constructors or destructors.  But, you can do this instead:

 # struct Vector3D
 # {
 #     double x, y, z;
 #
 #     static Vector3D opCall(double x, double y, double z)
 #     {
 #         Vector3D result;
 #         result.x = x; result.y = y; result.z = y;
 #         return result;
 #     }
 # }
 #
 # Vector3D a = Vector3D(1,2,3);

You've won me over. This certainly solves the "millions of temporaries being created" issue, for one!
 Three main ways, actually:

 1. Manually 'deleting' stuff: no fun at all.

Would it still be useful to do this sometimes in deconstructors if I know a few members that can safely be cleaned up immediately, so the garbage collector doesn't have to sift through things later? Or isn't this likely to give me any advantage at all?

It gives you an advantage if you *want* them cleaned up in a timely manner. If you just don't care, then you can leave them for the GC. There's a big catch to this, though: the GC will destroy objects in any old fashion; it's non-deterministic. The problem with this is: # class Foo # { # private Bar qux; // Bar is a class as well # # // ... # # ~this() # { # delete qux; # } # } That delete may fail! Because you can't tell what order the GC cleans stuff up, it's entirely possible that by the time Foo's destructor is called, 'qux' may have already been destroyed. HOWEVER, if you manually delete an object, or use 'auto', then you know the exact order in which objects are destroyed, and there's no problem. So the rule of thumb is: if a class is designed to be managed by the GC, don't try to do anything to member objects in the destructor (doing things like, say, freeing GL textures is fine since the GC doesn't manage those). On the other hand, if you always 'auto' or manually delete instances of a class, manually deleting member objects is fine. On a side note, unless I'm mistaken, the Ares project (which is an alternate standard library to Phobos) has support for passing an arg into destructors to indicate if they've been collected by the GC or explicitly deleted.
 2. Using 'auto'.  Any object variable marked with 'auto' will
 automatically destroy itself at the end of it's scope.  Example:

 # {
 #     // ...
 #
 #     auto Foo bar = new Foo;
 #
 #     // ...
 #
 #     {
 #
 #         auto Foo baz = new Foo;
 #
 #     } // baz is destroyed here
 #
 #     // ...
 #
 # } // bar is destroyed here

 One thing to watch out for:

 Auto-deleted variable:
 #    auto Foo bar = new Foo;

 Not auto-deleted, with type inference:
 #    auto bar = new Foo;

 'auto' without a type name after it means that D will work out the type
 of the variable itself.  Just watch out for it :)

 If you want to make sure instances of a particular class are always
 'auto', you can put the 'auto' keyword out the front of the class
 declaration:

 # auto class Foo
 # {
 #     // ...
 # }
 #
 # // MUST be declared auto:
 # auto Foo a;
 # Foo a; // <-- this won't compile

 However, keep in mind that 'auto' objects can't be put into 'out' or
 'inout' function arguments, you can't have global 'auto' variables, you
 can't have 'auto' members, and you can't return them out of functions
 [1].

 3. Using 'scope(exit)'.  You use 'scope(exit)' like a control statement:
 any statement (or block of statements) you put after a 'scope(exit)'
 will be executed when control leaves that scope.  For example:

 # {
 #
 #     // ...
 #
 #     Foo bar = new Foo;
 #     scope(exit) delete bar;
 #
 #     // ...
 #
 # } // 'delete bar' is executed here

 Please note that 'scope(exit)' blocks will execute irrespective of HOW
 you leave the scope: a return statement, break statement, goto or an
 exception.

 There's also 'scope(fail)' which only executes if you leave the scope
 via an exception, and 'scope(success)' for when you do NOT leave because
 of an exception :)

Noted and understood; I'll certainly be making much use of -all- of that! :)
 As for dealing with temporaries (which I know I haven't actually
 answered :P), then... we'll I don't know.  Unless you keep a reference
 to each temporary object in an expression, I suppose you'd have to leave
 the GC to handle that.

 That or, again, use structs.

Yeah, I think I'd be going for structs there. For one, I don't see it being an issue expect in cases like vectors and matrices where they're likely to be involved in a lot of number crunching.
 I'm assuming you mean using 'std.gc.disable()' when you say 'no gc
 active'.

 In that case, nothing is collected.  If you run out of memory, then you
 get an OutOfMemory exception.

I wasn't actually, because I was under the (now seemingly misguided) impression that the garbage collector wasn't initialized by default. And also the impression that it would be practical attempting to live without it. :P

Well, I'm not saying it's impossible; I've never tried to disable the GC. I've seen a few modified versions of Phobos lying around that completely omit the GC. Perhaps a compromise would be to see if you can live without it in *your* code, and then if you need the performance, create a specialized version of the standard library. Also, I'm only ~30% sure on this, but I think Ares (as mentioned above) avoids using the GC. That might be worth taking a gander at.
 If you don't already know: the D gc NEVER collects objects until you run
 out of memory.  That's when it does a collection.  One suggestion is to
 make a very low-priority thread that runs in the background, calling
 'std.gc.fullCollect()' over and over again to keep your memory profile
 from growing too large.

Ok. I'd interpreted "It is not predictable when a collection gets run, so the program can arbitrarily pause" (from http://www.digitalmars.com/d/garbage.html) as meaning that it could just 'decide' to kick in upon any operation that allocates memory, even if you weren't running low. From the same page: "All threads other than the collector thread must be halted while the collection is in progress." That seems a little worrying. Wouldn't this mean that running it in a low-priority thread wouldn't actually achieve much for a game? i.e. if the GC decides it wants to do some huge cleanup, then there's still no way to have it broken down, since the GC thread won't relinquish control until it's done?

Hmm. Funny, because I got the "low-priority thread" idea from http://digitalmars.com/d/phobos/std_gc.html (Checks) Aah, I had missed this little tidbit: "...so that if the program is idly waiting for input, memory can be cleaned up." Bugger. I wonder if it's possible to write a thread-safe collector :P
 Do you know if generational garbage collection using genCollect() would
 help, or is this just as likely to be arbitrarily 'greedy' for time? If
 not, is there an ideal solution for incremental garbage collection that
 could be built into D, or would this kill the GC's effectiveness
 completely?

If I remember correctly, genCollect() isn't implemented yet. That, or it's the same as calling fullCollect().
 
 One idea I once had was to keep an eye on the amount of memory your game
 is using.  At start up, compute (MaxMemory - CurrentlyUsedMemory).  If
 you're using between 0% - 20% of that, don't bother collecting.  If
 you're using 20% - 40%, run a very-low priority background collect.  If
 you get to 40% - 60%, run a higher-priority collect, etc.

 That way, the time spent running collections is proportional to how much
 memory you're using.  The cost of running the GC is amortized over time,
 instead of being in one great big lump [2].

Ok, re-thinking my above comment: does this mean that by continually forcing a full collection in a low-priority thread I could guarantee that individual garbage collections aren't going to take long? If so, wouldn't it then be feasible to force it constantly (every frame of the game) in my main thread anyway? [noted, snipped]

I'm guessing from how the sentence was phrased that the GC isn't thread-safe. When you think about it, it really couldn't be; otherwise other running threads could screw up the collect. What would be worth doing is maybe running some benchmarks to see how memory usage/amount of garbage affects the length of collects. If it's low enough, then maybe running a quick collect between every few frames might work... dunno tho. Accursed real-world facts! They always have to ruin good ideas :P
 Anyway, I hope this has helped, or at least given you some ideas.  Since
 I'm still learning D myself, take all of the above with a grain or two
 of salt.

Ok, I'll keep that in mind. I've even had the audacity to question some of your advice above! ;) Thanks for all your help; I appreciate your time, and sympathize with your blue-screen nightmares! :P

Well, hope I didn't confuse you too much with the threaded collects thing. Like I said, grain of salt :) -- Daniel -- v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 29 2006