🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Memory 'management' for dynamically sized objects

Started by
5 comments, last by cozzie 4 years, 4 months ago

Hey all,

I'm struggling a bit with defining the approach of handling allocation for the mesh instances in my 3d scene class. Some remarks:

  • I want to be able to identify them using a unique numeric ID (ideally the vector/array index)
  • don't need a full memory pool functionality, because the size in bytes for a single mesh instance object can vary
  • looking for a not overcomplex but in balance easy solution, can always improve later on

So far I've come up with 4 different options.
Question; what would you do/ what other options do you see?

Option 1:
Class members:
std::vector<C3dMeshInst> mMeshInstances>
std::vector<uint> mFreeList;

Initial loading:
- reserve(nrMeshInst in scene + some margin)
- load up all initial instances
- add remaining IDs in mFreelist

Add inst:
- Get ID from free list
- Assignment/copy, new mesh inst to free ID

Remove inst:
- 'reset' object in vector, assignment/copy = C3dMeshInst();
- add ID (vector index) to freelist

Option 2:
Class members:
C3dMeshInst *mMeshInstances;
std::vector<uint> mFreeList;

Initial:
- mMeshInstances = new C3dMeshInst(nrMeshInst in scene + some margin)
- load up all initial instances
- add remaining IDs in mFreeList

Add inst:
- Get ID from freelist
- Assignment/copy, new mesh inst to free ID

Remove inst:
- 'reset', by assignment/copy = C3dMeshInst();
- add ID (vector index) to freelist

Option 3:
Class members:
std::vector<C3dMeshInst*> mMeshInstances;
std::vector<uint> mFreeList;

Initial:
- resize mMeshInstances, nrMeshInst in scene + margin
- load up all initial instances, using new C3dMeshInst and assigning pointers in vector

Add inst:
- get vector index from freelist
- 'new' the new mesh inst, assign to pointer in vector

Remove inst:
- 'delete' the mesh inst
- add vector index to mFreeList

Option 4:
see 3, but:
- without freelist
- when removing just move .back() to deleted index
- when adding just push_back new pointer
- downside: ID is no longer guaranteed from vector index (might need ID var in C3dMeshInst class)

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Advertisement

Setting aside the question of why you would want to do this in the first place, I think option 1 is probably the least bad of the lot, especially if the actual mesh object is relatively small. Option 2 involves badly reinventing std::vector, options 3 and 4 involve unnecessary memory fragmentation, and option 4 especially involves giving up quick look-up for trivial amount of memory savings.

Not sure what you are trying to gain?

If `C3dMeshInst` is doing internal allocations anyway and is itself fairly small, then keeping an array of them doesn't seem to add much. And if you have an ID system that can't detect delete+reuse and your not resizing the array, then what is the benefit over a pointer?

2 I see no benefit over 1, if you want a dynamic array at least consider `std::unique_ptr<T[]>`. If you know the max size in advance an array or `std::array<T, N>`. But practically you save a few bytes of memory overhead (vector size and capacity values) and a few instructions (size check in `push_back` etc.) which doesn't seem significant at all.

3 and 4 seem like even more memory allocations, possibly depending on your use case that is OK (how many you have/create/destroy, target hardware), but I think then you are probably better off just storing the `C3dMeshInst` itself where you need it directly, rather than having this centralised thing.

4 even more so as now ID→Pointer translation is complicated which will lose performance, and not seeing what you gained doing that.

You could add the delete+reuse catch while still being smaller than a pointer by using some of the bits of the ID as a “version” number to make all IDs unique, then you have some benefit of making a `is_valid(id)` function possible assuming that is useful elsewhere.

Thanks both, I'm going for option 1.
Some answers:

  • why? because my current approach does (in this specific case) 5000+ push_back() calls, which I believe is pretty bad practice
    (compared to allocating the vector at once and doing simple assignments: https://www.acodersjourney.com/6-tips-supercharge-cpp-11-vector-performance/
    • I also don't support any form of adding and removing mesh instances
  • “And if you have an ID system that can't detect delete+reuse and your not resizing the array, then what is the benefit over a pointer?”
    Well, the vector index == the ID that's used for the mesh instances, so imagine a scene that's created upfront/offline, which contains a number of mesh instances, those all have IDs which I use for several purposes. At some point when I add game objects, I'll have to solve this differently, but for now the vector index is used in a lot of places (also for the renderqueue and spatial subdivision). The benefit over std::vector<object> versus std::vector<object*>, I guess, is that I don't have to new/delete everything manually? (instead, resize the vector just once, allocating all that space in one go)
  • the advantage over using std array is that I don't have to keep to a max

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

A common pattern has a store of objects, a proxy, a loader, and a cache. Individual parts go by many different names,but the abstract pattern is clear in all major engines and many popular APIs.

You may request a resource from the store. It returns a proxy in some form immediately, such as a handle, an intermediate object reflecting the resources state, or similar. The key is it does not block execution and gives you enough to work with. The loader schedules reading from disk or network or wherever and swapping out the proxy when loading is complete. The cache handles unloading when it is no longer used and/or space is low. Outdated proxies/handles can trigger reloading, or give empty objects, or a minimized set of data, or whatever makes sense for the system.

The pattern works for just about any resource type. Advanced proxy objects handle things like streaming levels of detail or running without graphics on servers and automation tools.

Thanks, will read some more into that

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

This topic is closed to new replies.

Advertisement