Quick throwaway objects

Started by
8 comments, last by me22 14 years, 4 months ago
I was wondering what the best way of handling potentially thousands of throwaway objects being created and destroyed each frame. I would think that it would be rather inefficient and cause memory thrashing and would rather be avoided. Maybe I should explain some of my intent and see what you guys might have to say. I was considering trying to make a multi-threaded game engine with graphics as one thread, physics for another, game entities grouped into 4 to 6 threads, etc. When a game entity needs to update some aspect of the graphics I thought of creating command objects (owning data necessary for its task and overriding an exec() function) that can be pushed onto a queue in the graphics thread where the graphics thread can call these commands when it's ready to then get rid of them. It makes me feel a little uncomfortable creating and destroying so many objects so rapidly and I assume it would not be very efficient. Do you guys have any thoughts on this? Maybe there is a way to make sure memory is not thrashed about too badly? Thanks.
Advertisement
Take a look into memory pools :)
Thanks. I should have thought about memory pools. For some reason I didn't. Thanks.
If the command objects are always the same size, get processed in FIFO order, and you can impose an upper bound on the number of outstanding commands at one time (all seem like reasonable assumptions, but maybe not possible in your scenario), you can use a circular buffer. For this specialized scenario it would be even faster than a pool.
You didn't mention what language you're using, but I'd also recomment memory pools. That said, if you are using c++ you could maybe even get by with just using std::queue or maybe a std::priority_queue (both of which are just adapted from std::vector, I believe) depending on what criteria you have for a command to be removed from the container.

std::vector will not allocate or dealocate memory as long as you have, more or less, an upperbound number of objects being created/destroyed. That is, you might not even need to use a pool if there are constraints on how you add/remove your command objects from your 'queue'.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Quote:Original post by ArmchairArmada
I was considering trying to make a multi-threaded game engine ... When a game entity needs to update some aspect of the graphics I thought of creating command objects (owning data necessary for its task and overriding an exec() function) that can be pushed onto a queue ... It makes me feel a little uncomfortable creating and destroying so many objects so rapidly and I assume it would not be very efficient.
I do exactly this in my code, and yes, 1000 calls to new/delete (which is slow normally, but is even slower in multithreaded code as locks/unlocks a mutex by default!) was abominably slow!

You could use a boost::pool, but even that is overkill. I ended up using a simpler "mark and release" allocator.
My "temporary heap" class (i.e. a heap for temporary objects) has a std::vector<char> member, which is resized to a few megabytes (or however much maximum temporary storage you need in any given frame) and also has an integer "usage" member, which marks where the next free space in this vector is.
Each new'ed object simply allocates it's memory by incrementing this integer. To release all the memory at the end of the frame you just set the integer back to 0. Further, operator delete doesn't actually release any memory, you have to manually release this heap once per frame (and hope you're not using any of the objects allocated in it any more!).

Switching to this scheme over the default new/delete gave over a 1000x speed up in my stress test!

Here's the thread where gamedev helped me figure out the allocation requirements for writing this allocator: http://www.gamedev.net/community/forums/topic.asp?topic_id=533865
boost::pool is no longer recommended for use because it hasn't been maintained in a very, very long time. As far as new/delete resulting in mutex acquisitions you should take a look at intel thread building blocks. It provides pretty much the fastest known allocator which internally operates by whenever possible allocating from thread local heaps to avoid the need to acquire mutexes. I've seen cases where doing nothing but overloading global new/delete to use intel tbb has provided a 10-15% speedup in an entire program.
Maybe you should look into the Fly Weight Pattern.

http://en.wikipedia.org/wiki/Flyweight_pattern
http://www.boost.org/doc/libs/1_41_0/libs/flyweight/doc/index.html
http://www.blackwasp.co.uk/Flyweight.aspx
If you can guarantee all these commands will be processed by the end of the frame, you can use a stack allocator (or linear allocator). It's as simple as they come, but also really handy for temporary memory. You can instantiate a stack allocator to use as a single frame allocator, and instead of deallocating memory individually, at the end of the frame, simply move the stack pointer back to the start.

Doesn't suffer from any fragmentation problems, can allocate any size object, and it's incredibly fast, especially if you don't need to call destructors on your objects. You can also implement two of them in a double-buffered fashion if you want memory from frame N to exist for frame N and frame N+1. Can also be handy.
-- gekko
Quote:Original post by nobodynews
You didn't mention what language you're using, but I'd also recomment memory pools. That said, if you are using c++ you could maybe even get by with just using std::queue or maybe a std::priority_queue (both of which are just adapted from std::vector, I believe) depending on what criteria you have for a command to be removed from the container.


std::queue defaults to a std::deque, since it needs pop_front.

std::priority_queue is a wrapper around the various _heap functions in <algorithm>, so it doesn't need _front operations, so it does default to std::vector.

This topic is closed to new replies.

Advertisement