Page 1 of 1

Custom memory allocators

Posted: Fri May 16, 2008 10:33 pm
by sinbad
This feature point is well overdue and has been languishing since my student form last year's Summer of Code didn't come back in the end. I've had it on my 'you really ought to do this' list for some time, but now we're getting down to the wire I decided to bite the bullet and try to get it in for 1.6 as was originally promised. I had some recent feedback from someone who has some experience in this area and while I don't have any code I can use from them, they indicated to me the sorts of things I should be looking at to improve the SoC version.

I've taken what was done in SoC last year and stripped it down to something leaner and more manageable - the original intention was to have a framework suitable for new leak detection / memory profiling too but there's no time to polish that, and besides many allocator systems you might want to re-use already have that built-in. In addition, some of what was done in SoC was good in theory, and based on sound principles, but after taking advice it was a little too flexible, and all those template instantiations would have an additional (code size) cost that wasn't justified. It was also a little over complicated to implement a new allocator.

I've got a version working and it's looking good for inclusion in 1.6. There are essentially 2 issues:

1) Overriding new/delete for Ogre classes
2) Overriding the STL container allocators

I've decided to have 2 different 'holder' classes for this, Ogre::AllocatedObject (suitable for subclassing objects from) and Ogre::STLAllocator (suitable for being passed as a template parameter to an STL container). Both are templated classes but take a simple 1-level set of parameters (instead of the multi-level used previously), most importantly identifying the 'policy' class which actually does the allocation. This class only has to implement allocation, deallocation and be able to report the maximum size of memory it can allocate in a single block (required for STL containers). The custom allocator is thus much simpler than before, and the same policy class can be a back-end to both AllocatedObject and STLAllocator, although you can define as many as you like.

I plan to have be 2 built-in policies OOTB, one simple one which uses malloc/free as usual, and one which uses nedmalloc. Users are free to add their own of course, provided you rebuild Ogre with your allocators configured.

For maximum flexibility, I will be typedeffing all the allocators individually (e.g. EntityAlloc, SceneNodeAlloc, PointerVectorAlloc) so that people can tweak them individually. All the defaults will point back to the same type but obviously with some judicious typedefs you could make allocators which allocate specific types of objects from particular pools or using particular strategies.

It may be a couple of days until you see anything in SVN, I'm ping-ponging between Windows, OS X and Linux to make sure I don't break anything as I get the main core up and running. But I'm pretty confident it should be ready for 1.6 as originally promised.

Posted: Fri May 16, 2008 11:28 pm
by Assaf Raman
Sounds good.

Re: Custom memory allocators

Posted: Sat May 17, 2008 2:21 am
by Game_Ender
sinbad wrote:It may be a couple of days until you see anything in SVN, I'm ping-ponging between Windows, OS X and Linux to make sure I don't break anything as I get the main core up and running. But I'm pretty confident it should be ready for 1.6 as originally promised.
Do you mean in trunk? Seems like doing it in stages on a branch might be the safest way.

EDIT: I forgot to mention, this is very cool and does give me inspiration for my own project and when I need to work on memory management.

Posted: Sun May 18, 2008 10:57 pm
by sinbad
No, I meant 1.6.

I have both categorised (by type of memory, e.g. geometry, scene objects etc, suitable for allocators that want to pool) and aligned memory allocators working, and hooks for both the standard malloc/free and nedmalloc. I'm testing on all 3 platforms, and for now I'm leaving the standard memory allocator as the default, so nothing really changes except the hooks being there. I should be able to get this starting framework in later today, and I'll expand it further in the next week. Right now I've only hooked up a few examples:

- Entity - simplest object example
- all Node subclasses - tests deep subclassing and specialisation in plugins
- EdgeData::TriangleFaceNormalList - tests STL container allocation, and it requires SIMD alignment
- All hardware buffer local memory copies - categorized, SIMD aligned memory allocated dynamically rather than via operator new/delete
- GL scratch buffers - as above but with manual 32-byte alignment

So all the main bases are covered and seem to be ok. I still have to test hooking up leak detection, allow categorised primitive type allocations, and of course categorise all the other objects and random memory allocations in the rest of the code. But, it's looking good.

Here's an example of how you customise the allocators:

Code: Select all

// Globally repoint all allocation for all categories to StdAllocPolicy
template <MemoryCategory Cat> class CategorisedAllocPolicy : public StdAllocPolicy{};

// Point allocations related to geometry to a different class
template <> class CategorisedAllocPolicy<MEMCATEGORY_GEOMETRY> : public YourGeomAllocPolicy{};
The allocation policies just have to implement static allocateBytes / deallocateBytes methods (plus an info method to return the largest allocation they can manage, something STL requires). There are alignment specific versions too but you can look at the final code for that. You'll have a huge amount of flexibility in re-pointing no just allocation as a whole, but allocation for particular categories or even for individual classes if you so choose. Should hopefully cater for everyone's needs - I've gone through quite a few iterations on this and have a flexibility / ease / elegance balance that I'm pretty happy with now.

Posted: Mon May 19, 2008 12:12 am
by Praetor
Sounds awesome sinbad. That's a mighty big feature that I hope those console devs especially appreciate.

Posted: Mon May 19, 2008 9:46 am
by sinbad
First commit is in so you can see it in action - I've tested on VC8, Ubuntu 7.1 and OS X 10.4. More to come.

Posted: Sun Aug 10, 2008 8:55 pm
by spookyboo
I wonder whether I should change (almost) all of my new/delete's into the OGRE_NEW / OGRE_DELETE versions. If I understand it correctly, all ´ogre´ allocations are checked by the memory tracker, so I don´t have the use VC to spot any memory leaks. Some advice is appreciated.

Posted: Mon Aug 11, 2008 2:02 am
by sinbad
OGRE_NEW / OGRE_DELETE should be used where a class is derived from AllocatedObject. You then get all memory leak tracking for free.

If the class doesn't derive from AllocatedObject, then you need to use OGRE_NEW_T or OGRE_ALLOC_T instead, depending on whether you need constructors / destructors or not.

Posted: Fri Aug 22, 2008 8:15 pm
by CABAListic
I stumbled upon a problem with the new memory allocation policy in Ogre's Image::loadDynamicImage. I'm using this to manually create images, so I create an array of uchars which I pass to the function and tell it to claim ownership of the array so that I don't have to track the array alongside the image for deletion.

The problem now is that I was using new[] to create the array, but Ogre::Image now internally uses the memory macros. So when the Image's destructor is called, it would use OGRE_FREE_T on the array supplied by me which triggers an assertion in debug mode because the array was not created via OGRE_ALLOC_T. So the array now has to be created via OGRE_ALLOC_T.
I don't know if that requirement presents much of a problem, but it really must be documented, because it will break existing code.

Posted: Fri Aug 22, 2008 9:36 pm
by sinbad
Yeah, I'd already documented several instances of this in the header files and in the porting notes, but it looks like I missed this method. Will add.

[edit]Ok I had added it to the porting notes already, just not the header.
Porting Notes wrote: If you transfer ownership of data to / from Image via the loadDynamicImage method, you must make sure that memory is allocated and freed consistently. Again, allocate and free via OGRE_ALLOC_T or OGRE_MALLOC and OGRE_FREE and use the MEMCATEGORY_GENERAL category.
Please remember to check the porting notes! :) I do try to keep them up to date.

Posted: Fri Aug 22, 2008 9:55 pm
by CABAListic
D'oh, yeah, totally forgot about porting notes. I've only just recently set up a version of Shoggoth on my PC because some users reported that error with ETM, so I was in a bit of a rush here :)

Posted: Fri Aug 22, 2008 10:00 pm
by spookyboo
I have this template function

Code: Select all

template <class T>
T* createDynamicAttribute(void)
{
    return new T();
};
and replaced it with

Code: Select all

template <class T>
T* createDynamicAttribute(void)
{
    return OGRE_NEW_T(T, MEMCATEGORY_GENERAL)();
};
The delete however gives me problems. I replaced

Code: Select all

delete dynamicAttribute;
with

Code: Select all

OGRE_DELETE_T(dynamicAttribute, DynamicAttribute, MEMCATEGORY_GENERAL);
I know that 'dynamicAttribute' is always a subclass of DynamicAttribute (is it really necessary to provide the class?)
The above gives a an exception at memory location .... Am I doing something wrong here?

<edit>Nevermind, I realised that there is still a regular 'delete' which screws things up.</edit>

Posted: Sat Aug 23, 2008 2:41 pm
by sinbad
spookyboo wrote:I know that 'dynamicAttribute' is always a subclass of DynamicAttribute (is it really necessary to provide the class?)
Yes, this is because placement new/delete requires that the delete manually calls the destructor - this type statement is essentially telling it what destructor to call for you, we can't possibly know what that is. It's possible we could add some kind of templated wrapper to do it for you, although actually that's what SharedPtr<T> does (which now has an option for destroying using OGRE_DELETE_T).

If you know you don't have constructors / destructors to call then OGRE_ALLOC_T / OGRE_FREE is the way to go. Or, make your class a subclass of AllocatedObject and then you can just use the much more simple form of OGRE_NEW / OGRE_DELETE.