Memory management of resources

Started by
6 comments, last by haegarr 9 years, 12 months ago

Hi,

I know this question had probably been asked a few times and I did search the forums but couldn't find what I'm after.

My question concerns memory allocation (of resources in a game) and how to avoid having to manage them manually.

At first, my little game framework had (almost) everything wrapped in std::shared_ptr. I think this has impacted performance (AFAIR I did profile it) and also leads to bad design. Recently I removed every use of std::shared_ptr in favor of raw pointers and started to actually think about ownership of memory to define who has the responsibility to free the resources. I have a class called ResourcePool which given a type of a resource, it allocated it and is the owner of the resource and therefore responsible to free its memory. But what if for instance, I would like to clone a resource (because I'm going to change it and don't want everything which uses this resource to be affected). The cloned resource wasn't created using the ResourcePool (The resource pool can create resource only from files) and therefore won't be freed by it which leaves the responsibility to the user. I can think of three solutions to the problem:

1. Use shared_ptr (or probably my own intrusive reference counting) but that's what I was try to avoid in the first place.

2. Leave it to the user, but in the long run is cumbersome and would cause memory leaks if not carefully taken care of

3. Add the resource to the ResourcePool (or other class) which will become the owner of the resource and therefore will free it.

I hope I was clear enough and would like to hear what other designs have you guys used to avoid having to manually manage memory in your game

Thanks

Advertisement

I prefer #4 ;)

With the single responsibility principle in mind, there are several roles involved:

a) A resource pool in the sense of memory management is usually an instance that holds a couple of objects pre-allocated and ready to use. A client asks the pool for an instance, and the pool returns one of the hold instances if possible (so there is no real memory allocation). The pool may be exhausted at the moment, so that a real memory allocation is done and the result is returned. A client that no longer needs the object gives it back to the pool. The pool checks whether a set maximal amount of objects is already hold, and adds the returned object to the pool if not, but frees the object is the pool "is full".

b) A resource cache is an instance that holds a specific, ready to use object. A client requests a resource from the cache, the cache looks up whether the requested resource is already available, and returns it if so. Otherwise it invokes the resource loader and integrates the returned resource into the cache. If the last using client gives a specific resource back (and the resource is not marked as statically cached), the cache gives the object back to the pool.

c) A resource loader is an instance that requests the resource pool to return a resource object, loads the resource data from disk, initializes the resource object accordingly, and returns the resource object to the client.

I'm going to explain my minamalistic approch for handling graphics API resources. It is applicable for assets I think.

I have an interface IGraphicsResource that holds ID3D11DeviceChild/GLuint.

Later the real resource classes inherits IGraphicsResource.

IGraphicsResource also hold an integer that tells how many object are currently using that resource.

There is a class ResourceProxy that know when to increase/decrase the proxy counter and holds a pointer to the resource(works like shared ptr).

So it looks something like this:


class ProxyCounter
{
public :

ProxyCounter() : 
m_numProxies(0)
{}

void ProxyIncrease() { m_numProxies++; }
void ProxyDecrease() { m_numProxies--; }
U32 GetProxyCount() { return m_numProxies; }


private:
U32 m_numProxies;
};

class IGraphicsResource : public ProxyCounter, public TNoncopyable
{
public:

IGraphicsResource() {}

//you can implement this as you wish
//~IGraphicsResource() { Destroy(); } //not really needed I will explain why
virtual void Destroy() = 0 { m_resourceHandle.Release(); }; 

virtual EResourceType GetResurceType() const = 0;

protected : 

TComPtr<ID3D11DeviceChild> m_resourceHandle;
};template <typename T>
template <class T>
class ResourcePorxy
{
public :
//-------------------------------------------------------
// ShaderPtr-ish copy/assign/const/dest functions
//-------------------------------------------------------
T* operator->() { return m_pResourceInstance; }
const T* operator->() const { return m_pResourceInstance; }
private :
void ProxyIncrease() { if (m_pResourceInstance) m_pResourceInstance->ProxyIncrease(); }
void ProxyDecrease() { if (m_pResourceInstance) m_pResourceInstance->ProxyDecrease(); m_pResourceInstance = NULL; }
T* m_pResourceInstance;
};


class TextureResource : public IGraphicsResource
{
public : 

TextureResource()  { }
ResultCode Create(GraphicsDevice* pDevice, const TextureDesc& desc);

//implementing stuff
void Destroy() { }
EResourceType GetResurceType() const { return EResourceType::Texture; 

private : 
TComPtr<ID3D11ShaderResourceView> m_d3d11ResourceView;
TComPtr<ID3D11RenderTargetView> m_d3d11RenderTargetView;
TComPtr<ID3D11DepthStencilView> m_d3d11DepthStencilView;
};

How I use this...

So lets say that Resource Manager holds all the TextureResources

class ResMngr

{

//map,hash_map,vector wateva

ResourceContainerType<TextureResource*> m_Resources;

void AddNewResource(TextureResource* ptrOrSomething, .....);

//It is save to cast any resurce type to IGraphicsResource

//there is a GetType method in IGraphicsResource so you can add a template method probably?

ResourcePorxy<TextureResource> GetTexture(ParamType p) { return m_Resources.get(p); }

}

If I want to add a new resource i will write something like this;

TextureResource* pNewResource = new TextureResource;

pNewResource->CreateTexture();

ResourceManager.Add(pNewResource, ....)

//probably later when drawing ....

ResourceProxy<TextureResource> texProxy = ResourceManager.GetResource(....);

texProxy ->BInd();

//draw...

//later when we load the next level

//somewhere in ResourceManager::CleanUp()

{

//Delete all unused resources but always keep the main character resource loaded

for_each_resource_do(){

if(!isPlayerResource(resource) && resource.GetProxyCount() == 0) DeleteRes(resource);

}

}

Hope that helps...

PS. I hate that message editor

Thank you guys for the responses

@imoogiBG - If I understand you correctly, you are basically using intrusive reference counting inside your resource classes?

@haegarr: I agree with the sole responsibility principle but I still can't see how you would solve the problem I described. Given a resource that wasn't loaded using the resource loader but was manually created how would you go about and manage its memory? or are you saying that every memory allocation would go through the resource pool?


@haegarr: I agree with the sole responsibility principle but I still can't see how you would solve the problem I described. Given a resource that wasn't loaded using the resource loader but was manually created how would you go about and manage its memory? or are you saying that every memory allocation would go through the resource pool?

Yes, that is what I say. Although I should have noticed that the resource pool is one specialization of memory allocation in general. The pool strategy works fine for object allocations on the heap, and its use reduces the amount of invocations of new and delete. Other use cases like e.g. graphics API provided buffers will need other strategies, of course, but can also be hidden behind an allocator interface.

Considering your problem, a resource generator works more or less identical to a resource loader when seen from the outside: It provides a resource object.

While the memory ownership is given to the allocator (e.g. a pool), the management of a resource lifecycle as an object may be different in dependence on whether a generator or a loader was the supplier. For static resources it is typical that the resource is loaded from mass storage and, due to the lag of mass storage compared to RAM, hold in a cache. Strictly seen, such a cache is just one possible implementation of a resource runtime storage. So it is arguable that in fact the interface to resources is given by a resource library, and such a library may use a resource cache as internal storage solution. (Such a library is often named a resource manager, but IMO the composite of library, cache, allocator, loader, and perhaps others only together define the manager.)

Thank you guys for the responses

@imoogiBG - If I understand you correctly, you are basically using intrusive reference counting inside your resource classes?

Yep something like that.

Some Pros:

- I can use raw pointers if needed, all I need to do is to manage the proxy counter by myself, or If I'm shure that the resouce will be alive during some operation I don't have to deal with any reference counting.

- ResourceProxy and IGraphicsResource are flexible (because they are small classes written by you), so you can easily add some fancy allocations (like memory pools).

- You can get rid of all virtual functions a using switch smile.png

@haegarr I think your concept of a resource pool is too much for my needs. It seems to me that the resource pool purpose is to reduce the dynamic allocations using new / delete but for now that isn't an issue. And besides, even when using the resource pool I'll still have to manage the memory manually but instead of calling delete on the pointer, I will need to tell the resource pool that the memory is not used anymore.

I'm considering going with an intrusive reference counting approach. It seems to me to have the most convenient usage with not too much overhead...


@haegarr I think your concept of a resource pool is too much for my needs. It seems to me that the resource pool purpose is to reduce the dynamic allocations using new / delete but for now that isn't an issue. And besides, even when using the resource pool I'll still have to manage the memory manually but instead of calling delete on the pointer, I will need to tell the resource pool that the memory is not used anymore.

Even worse (from the standpoint of overhead): The client need to return the resource to the library. Only if the library determines the returned resource to be no longer in use (e.g. by internal reference counting) and the usage policy allows the resource to be unloaded (e.g. the resource is not marked to remain for the runtime of the entire game, the runtime of the current level, or similar situations), then the resource is unloaded and the memory returned to the allocator. The allocator then decides how to handle the memory, perhaps returning it to the free heap.


I'm considering going with an intrusive reference counting approach. It seems to me to have the most convenient usage with not too much overhead...

Of course, a concept like described has many facets, and not all of them are needed in every situation. Using a reference counting system without caching and pooling is fine if resource generation time is not a critical factor (as long as you have at least an interface where resource sharing takes place).

This topic is closed to new replies.

Advertisement