Memory management dilemma
Daniel Keep
daniel.keep.lists at gmail.com
Sun May 28 08:15:52 PDT 2006
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/
More information about the Digitalmars-d
mailing list