{"id":19564342,"url":"https://github.com/ronenness/dcm_pool","last_synced_at":"2025-04-27T00:32:57.905Z","repository":{"id":82588216,"uuid":"124387528","full_name":"RonenNess/dcm_pool","owner":"RonenNess","description":"Dynamic, Contiguous-Memory Objects Pool","archived":false,"fork":false,"pushed_at":"2018-03-09T11:49:04.000Z","size":42,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-04T19:08:39.123Z","etag":null,"topics":["cpp","cpp-library","gamedev","pooling","templates"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RonenNess.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-08T12:22:21.000Z","updated_at":"2022-11-23T11:55:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"f04bb385-35ce-41e5-b4b7-afc3e0fbf6dd","html_url":"https://github.com/RonenNess/dcm_pool","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RonenNess%2Fdcm_pool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RonenNess%2Fdcm_pool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RonenNess%2Fdcm_pool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RonenNess%2Fdcm_pool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RonenNess","download_url":"https://codeload.github.com/RonenNess/dcm_pool/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251073595,"owners_count":21532005,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp-library","gamedev","pooling","templates"],"created_at":"2024-11-11T05:21:34.553Z","updated_at":"2025-04-27T00:32:57.897Z","avatar_url":"https://github.com/RonenNess.png","language":"C++","readme":"# dcm_pool\nDynamic, Contiguous-Memory Objects Pool.\n\n## What is it\n\nA dynamic contiguous-memory Pool, or in short a dcm_pool, is a pool of objects that guarantee the following:\n\n1. All objects are kept in memory in a contiguous memory block, and iterating them is equivalent to iterating a vector.\n2. Accessing objects by pointer is O(1).\n3. Allocating new objects is O(1), and does not cause an actual 'new()' call every time.\n4. Releasing an object is O(1), and does not cause an actual 'delete()' call every time.\n5. The pool can be dynamic, you don't have to set a hard size limit or allocate up-front.\n\n## Why\n\nThis pool have very specific use cases, so best way to describe it is by example. \n\nLets say you're building a game engine with the [Entity-Component-System](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system) pattern, which is very common with video game engines. In ECS design you have a basic 'entity' object, to which you attach different 'components' that implement different features and behaviours. \n\nNow you have different type of components attached to different entities, and they all require an 'Update()' call every frame. You have no way to tell how many components you'll need, and you probably want to pool your components so that repeated assigning and releasing won't cause too many memory management calls. In addition, you want similar components to be in contiguous memory, to enjoy [memory access optimizations](https://bitbashing.io/memory-performance.html).\n\nIf you use a regular pool, you can easily reduce the new() and delete() calls, but your objects memory will not be contiguous and iterating them for the Update() loop would be less efficient. If you use a vector or other contiguous-memory container you'll enjoy a very fast iteration, but slow releasing / allocating (even if you use reserve - releasing might cause a shift of memory). In addition, using vector will not allow you to hold pointers or indexes to objects inside the pool, as those could change.\n\nThis lib provides a pool to answer just that: reduce new / delete calls, keep everything in contiguous memory, and allow direct access using pointers.\n\n## Install\n\ndcm_pool is a header-only lib, you don't need to compile any .lib or .dll files.\n\nTo use it, simply get the header files from [dcm_pool/include/](https://github.com/RonenNess/dcm_pool/tree/master/dcm_pool/include/) and make them available to your compiler.\n\n## Usage\n\nFirst lets take a look at a full, simple example:\n\n```cpp\n#include \u003cdcm_pool/dcm_pool.h\u003e\nusing namespace dcm_pool;\n\n// a dog class - the objects we want to pool\nclass Dog\n{\npublic:\n\n\t// what dogs do every frame\n\tvoid Update()\n\t{\n\t\tcout \u003c\u003c \"Woof!\" \u003c\u003c endl;\n\t}\n\n\t// rub the dog's belly\n\tvoid RubBelly()\n\t{\n\t\tcout \u003c\u003c \"Your happiness increased by 5%.\" \u003c\u003c endl;\n\t}\n};\n\n// main\nvoid main()\n{\n\t// create a pool of dogs\n\tDcmPool\u003cDog\u003e pool;\n\t\n\t// alloc a new dog and rub its belly\n\tauto new_dog = pool.Alloc();\n\tnew_dog-\u003eRubBelly();\n\t\n\t// create another dog \n\tauto new_dog2 = pool.Alloc();\n\t\n\t// release the first dog\n\tpool.Release(new_dog);\n\t\n\t// iterate over all dogs in pool and update (using lambda)\n\tpool.Iterate([](Dog\u0026 dog, ObjectId id) { dog.Update(); });\n}\n```\n\nNow lets dive into more details:\n\n\n### Creating A Pool\n\nCreating a new pool is easy:\n\n```cpp\nDcmPool\u003cMyObjectType\u003e pool;\n```\n\nNote that its best to use the object type itself, and not a reference or a pointer. If you use pointers you'll lose some of the memory-access performance.\n\nThe objects pool constructor receive several optional params to help you fine-tune its behaviour:\n\n```cpp\nDcmPool(size_t max_size = 0, size_t reserve = 0, size_t shrink_threshold = 1024, DefragModes defrag_mode = DEFRAG_DEFERRED);\n```\n\n- **max_size**: If provided, will limit the pool size (throw exception if exceed limit).\n- **reserve**: If provided, will reserve this amount of objects capacity in internal vector.\n- **shrink_threshold**: While the pool grows dynamically, we only shrink the pool's memory chunk when having this amount of free objects in pool.\n- **defrag_mode**: When to handle defragging - immediately on release, when trying to iterate objects, or manually.\n\nYou can understand from the params above that if you want a constant-size pool you can set `reserve` and `max_size` to the same value, and you'll have 0 new() / delete() calls.\n\n### Allocating \u0026 Releasing\n\nTo allocate a new object from pool you need to use ```Alloc```:\n\n```cpp\nauto newobj = pool.Alloc();\n```\n\nNote that this will not invoke the object's constructor. If you want an initialize function you need to define one and call it manually.\n\nThe returned value of ```Alloc``` is a pointer-like object that provide a direct access to the object in pool. Even after defragging, the pointer will not lose its reference (but never try to grab the actual address of the object as it might change internally).\n\nAccessing the pointer directly is not as efficient as using a regular pointer and may require a single hash-table lookup, if the pool was defragged since you last used it.\n\nWhen you're done with the object and want to release it, use ```Release```:\n\n```cpp\npool.Release(newobj);\n```\n\nSimilar to with ```Alloc```, this will not call the object destructor.\n\n### Iterating Pool\n\nThe main way to iterate a pool is via the ```Iterate``` function. With a lambda, it looks like this:\n\n```cpp\npool.Iterate([](MyObjectType\u0026 obj, ObjectId id) { /* do something with object */ });\n```\n\nNote the extra param, ObjectId. This id represent the unique, constant id of the object in pool. You can use this id to create new object pointers or release the object using id instead of pointer.\n\nIf you want to use a regular function and not a lambda, the example above would look like this:\n\n```cpp\n// iteration callback\nvoid update_loop(MyObjectType\u0026 obj, ObjectId id)\n{\n\t// your update code here\n}\n\n// using the iteration callback:\npool.Iterate(update_loop);\n```\n\nOr if you need more control, you can use the ```IterateEx``` version:\n\n```cpp\n// iteration callback\nIterationReturnCode update_loop(MyObjectType\u0026 obj, ObjectId id, DcmPool\u003cMyObjectType\u003e\u0026 pool)\n{\n\t// your update code here\n\t\n\t// continue to next object\n\treturn IterationReturnCode::ITER_CONTINUE;\n}\n\n// using the iteration callback:\npool.IterateEx(update_loop);\n```\n\nNote that for const pools you have a corresponding iteration function that receive a similar signature but with const references.\n\n### Handling Init / Terminate Automatically\n\nAs mentioned before, calling ```Alloc``` and ```Release``` does not guarantee corresponding constructor / destructor calls. So you can't count on ctor / dtor with your objects.\n\nHowever, dcm_pool provide a simple way to automatically invoke a custom Init / Terminate function whenever you ```Alloc``` or ```Release``` an object:\n\n```cpp\n// call obj.Init() whenever a new object is allocated\npool.OnAlloc = [](MyObjectType\u0026 obj, ObjectId id, DcmPool\u003cMyObjectType\u003e\u0026 pool) {\n\tobj.Init();\n};\n\n// call obj.Release() whenever an object is being released\npool.OnRelease = [](MyObjectType\u0026 obj, ObjectId id, DcmPool\u003cMyObjectType\u003e\u0026 pool) {\n\tobj.Release();\n};\n```\n\nJust like you would normally avoid raising exceptions from a normal constructor / destructor, you should avoid raising exceptions from your Init / Terminate as well, as it could cause undefined behavior with the pool.\n\n### Cleanup\n\nThe pool will shrink its memory chunk automatically when there are too many unused objects in pool. \nHowever, if you want to reduce the pool's size immediately, you can call:\n\n```cpp\npool.ClearUnusedMemory();\n```\n\nNote that if the pool is not defragged (eg have holes in it) it will raise exception.\n\n### Defragging\n\nAs mentioned before, the pool might have \"holes\" in its contiguous memory due to objects being released from the middle. To solve this, the dcm_pool do self-defragging.\n\nThe defragging process worst case takes O(N), where N is number of holes in the pool and not number of objects.\n\ndcm_pool Support 3 defragging modes:\n\n#### DEFRAG_IMMEDIATE\n\nWill close the hole immediately as its created. With this mode the performance is deterministic; The moment you release an object that creates a hole, it will be closed by moving the last object in its place.\n\n#### DEFRAG_DEFERRED\n\nIn this mode the pool will accumulate holes and only close them when trying to iterate the pool. The huge advantage here is that if you do something like Release -\u003e Alloc -\u003e Release -\u003e Alloc, in the two times you alloc it will just return the unused objects in the middle, and no will defragging will happen.\n\nThe disadvantage is that the performance of the Iteration() call becomes non-deterministic and may change depending on how many holes you have in your contiguous memory.\n\n#### DEFRAG_MANUAL\n\nIn this mode the pool will never defrag on its own. If you iterate a pool with holes it will just skip the unused objects, and you'll need to call ```pool.Defrag()``` manually when you think its right.\n\n## Limitations \u0026 Tips\n\n1. The objects you use in pool must have a default constructor.\n2. Allocating new objects will not invoke a constructor call. You need to create and call an Init() function manually.\n3. Releasing objects will not invoke a destructor call. You need to create and call a Dtor() function manually.\n4. As an extension of the constructor / destructor limitation, if you do implement them they shouldn't do any heavy lifting as they may be called internally.\n5. Implementing a Move Assignment Operator will increase performance significantly.\n6. To maximize the memory-based optimization, don't use the pool to store pointers or references. \n\nAs you can see the limitations above apply to most basic pooling solutions.\n\n## How does it work\n\n1. The pool uses a vector internally to store the objects in memory.\n2. The vector grows and shrink as the pool changes its size.\n3. When you release an object, it creates a 'hole' in the contiguous vector memory. \n\t3. a. That hole is closed during the defragging process. \n\t3. b. We can accumulate a list of holes if defragging mode is not immediate.\n\t3. c. Defragging takes O(N), where N is number of holes (not number of pool). We close holes by taking objects from the end of the vector.\n4. To iterate the vector you call Iterate(), which takes a function pointer to run on every valid object in pool.\n5. Since defragging shuffles memory, you can't access objects by index. To solve this, we use a special pointer class to allow access to specific objects:\n\t5. a. Every object in pool is asigned with a unique id.\n\t5. b. The pool keeps an internal hash table to convert id to actual index (hidden from the user).\n\t5. c. When the pointer tries to fetch the object it points on, if the pool was defragged since last access it use the hash table to find the new objects index.\n6. To store the list of holes in the vector we reuse the free objects, so no additional memory is wasted.\n\n## Memory Consumption\n\nIn addition to the objects themselves, dcm_pool adds additional unique id per object (an int) + a hash table to convert id to index.\n\n## Performance\n\nTo test the dcm_pool I created a simple object type that has an hp counter and an Update() function that randomly decreases it. When reaching 0, the object is dead and is removed.\n\nI simulate few 'Update' loops, where in every loop I create new 15000 objects, add them to pool, and iterate all the existing objects in pool to Update them. Then I measure how long it took to update everything, adding the new objects, and deleting the dead ones.\n\nI compared the 'dcm_pool' performance to a simple list and a vector naive implementation.\n\nThe test is not very 'fair' because the list and vector are used in a very naive way, but it still gives a general idea about how efficient 'dcm_pool' is.\n\nNote: Although possible, I didn't allocate memory upfront for the pool nor the vector.\n\nThe results are below:\n\n```\n==========================\nTEST DCM_POOL\n==========================\n\nFrame: 0\nLoops per frame: 15000\nPool current size: 1\nTotal update calls: 0\nIterations total time: 0\nAllocations total time: 0\nRemove objects total time: 0\nObjects removed this frame: 0\nObjects added this frame: 1\n--------------------------\nFrame: 1\nLoops per frame: 15000\nPool current size: 6950\nTotal update calls: 72430695\nIterations total time: 23.246\nAllocations total time: 0.155\nRemove objects total time: 0.159\nObjects removed this frame: 8051\nObjects added this frame: 15000\n--------------------------\nFrame: 2\nLoops per frame: 15000\nPool current size: 6970\nTotal update calls: 104489064\nIterations total time: 33.579\nAllocations total time: 0.286\nRemove objects total time: 0.277\nObjects removed this frame: 14980\nObjects added this frame: 15000\n--------------------------\nFrame: 3\nLoops per frame: 15000\nPool current size: 6950\nTotal update calls: 104463885\nIterations total time: 33.439\nAllocations total time: 0.406\nRemove objects total time: 0.28\nObjects removed this frame: 15020\nObjects added this frame: 15000\n--------------------------\nFrame: 4\nLoops per frame: 15000\nPool current size: 6831\nTotal update calls: 104115381\nIterations total time: 33.994\nAllocations total time: 0.531\nRemove objects total time: 0.262\nObjects removed this frame: 15119\nObjects added this frame: 15000\n--------------------------\n\n\n==========================\nTEST LIST\n==========================\n\nFrame: 0\nLoops per frame: 15000\nPool current size: 1\nTotal update calls: 1\nIterations total time: 0\nAllocations total time: 0\nRemove objects total time: 0\nObjects removed this frame: 0\nObjects added this frame: 1\n--------------------------\nFrame: 1\nLoops per frame: 15000\nPool current size: 6976\nTotal update calls: 72635680\nIterations total time: 51.905\nAllocations total time: 0.023\nRemove objects total time: 36.75\nObjects removed this frame: 8025\nObjects added this frame: 15000\n--------------------------\nFrame: 2\nLoops per frame: 15000\nPool current size: 6952\nTotal update calls: 104812066\nIterations total time: 75.659\nAllocations total time: 0.07\nRemove objects total time: 53.76\nObjects removed this frame: 15024\nObjects added this frame: 15000\n--------------------------\nFrame: 3\nLoops per frame: 15000\nPool current size: 6935\nTotal update calls: 104436172\nIterations total time: 75.233\nAllocations total time: 0.118\nRemove objects total time: 53.833\nObjects removed this frame: 15017\nObjects added this frame: 15000\n--------------------------\nFrame: 4\nLoops per frame: 15000\nPool current size: 6949\nTotal update calls: 104269843\nIterations total time: 73.96\nAllocations total time: 0.157\nRemove objects total time: 52.76\nObjects removed this frame: 14986\nObjects added this frame: 15000\n--------------------------\n\n\n==========================\nTEST VECTOR\n==========================\n\nFrame: 0\nLoops per frame: 15000\nPool current size: 1\nTotal update calls: 1\nIterations total time: 0\nAllocations total time: 0\nRemove objects total time: 0\nObjects removed this frame: 0\nObjects added this frame: 1\n--------------------------\nFrame: 1\nLoops per frame: 15000\nPool current size: 6872\nTotal update calls: 71811310\nIterations total time: 25.1\nAllocations total time: 0.019\nRemove objects total time: 12.152\nObjects removed this frame: 15735\nObjects added this frame: 15000\n--------------------------\nFrame: 2\nLoops per frame: 15000\nPool current size: 6957\nTotal update calls: 103485217\nIterations total time: 36.328\nAllocations total time: 0.031\nRemove objects total time: 17.489\nObjects removed this frame: 29915\nObjects added this frame: 15000\n--------------------------\nFrame: 3\nLoops per frame: 15000\nPool current size: 6972\nTotal update calls: 104276134\nIterations total time: 36.259\nAllocations total time: 0.052\nRemove objects total time: 17.383\nObjects removed this frame: 29985\nObjects added this frame: 15000\n--------------------------\nFrame: 4\nLoops per frame: 15000\nPool current size: 6918\nTotal update calls: 104338469\nIterations total time: 36.525\nAllocations total time: 0.065\nRemove objects total time: 17.633\nObjects removed this frame: 30054\nObjects added this frame: 15000\n--------------------------\n\n```\n\n#### Conclusion\n\nAt the time of peak (eg when there were the most objects in pools), we measured the following times (all in seconds):\n\n```\n                Dcm_pool        List        Vector    \nIterating       33.439          75.233      36.259\nAllocating      0.406           0.118       0.052\nReleasing       0.28            53.833      17.383\nUpdate Calls    104463885       104436172   104276134\n```\n\n\nNote: the Vector was iterated using an iterator. However, if we iterate the vector using a direct index its much faster and closer to the pool's performance.\n\n**If performance is pretty close to vector (despite in releasing) why not using a vector?**\n\nFrom the benchmark above you may come to the conclusion that a vector may be 'good enough', provided you don't have lots of releasing to do. However, keep in mind that you can't safely hold a pointer to an item inside a vector, since pushing / poping may change the underling addresses. The dcm_pool however, gives you vector-like performance but with faster releasing AND safe-to-use pointers to objects inside the pool.\n\n## License\n\ndcm_pool is distributed under the MIT license and can be used for any purpose.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fronenness%2Fdcm_pool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fronenness%2Fdcm_pool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fronenness%2Fdcm_pool/lists"}