├── .gitattributes ├── .gitignore ├── Pool.h ├── Tests.cpp ├── _clang-format └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Pool.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright(C) 2017-2018 Stepanov Dmitriy a.k.a mr.DIMAS a.k.a v1al 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files(the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions : 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | ============= 25 | Overview: 26 | Pool serves for the few goals: eliminates unnecessary memory allocations, 27 | preserves data locality, gives mechanism of control for object indices. 28 | 29 | This pool provides user the easy way of control for spawned objects through 30 | special handles. Why we can't use ordinary pointers? Answer is simple: when pool 31 | grows, it's memory block may change it's position in memory and all pointers 32 | will become invalid. To avoid this problem, we use handles. 33 | 34 | What is "handle"? Handle is something like index, but with additional 35 | information, that allows us to ensure that handle "points" to same object as 36 | before. This additional info called "stamp". When you asks pool for a new 37 | object, pool marks it with unique "stamp" and gives you handle with index of a 38 | new object and "stamp" of an object. Now if you want to ensure, that handle 39 | "points" to same object as before, you just compare "stamps" - if they are same, 40 | then handle is correct. 41 | 42 | Pool implementation is object-agnostic, so you can store any suitable object in 43 | it. It also may contain non-POD objects. 44 | 45 | ============= 46 | How it works: 47 | Firstly, pool allocates memory block with initial size = initialCapacity * 48 | recordSize, fills it with special marks, which indicates that memory piece is 49 | unused. Also pool creates list of indices of free memory blocks. 50 | 51 | When user calls Spawn method, pool pops index of free memory block, constructs 52 | object in it using placement new, makes new stamp and returns handle to the 53 | user. 54 | 55 | When user calls Return methos, pool returns index of the object to "free list", 56 | marks object as free and calls destructor of the object. 57 | 58 | ============= 59 | Notes: 60 | Your object should have constructor without arguments. 61 | Create pool with large enough capacity, because any Spawn method called on full 62 | pool will result in memory reallocation and memory movement, which is quite 63 | expensive operation 64 | 65 | ============= 66 | Q&A: 67 | 68 | Q: How fast is Spawn method? 69 | A: Spawn method has amortized complexity of O(1). 70 | 71 | Q: How fast is Return method? 72 | A: Return method has amortized complexity of O(1). 73 | 74 | ============= 75 | Example: 76 | 77 | class Foo { 78 | private: 79 | int fooBar; 80 | public: 81 | Foo() {} 82 | Foo(int foobar) : fooBar(foobar) {} 83 | }; 84 | 85 | Pool pool(1024); // make pool with default capacity of 1024 objects 86 | Handle bar = pool.Spawn(); // spawn object with default constructor 87 | Handle baz = pool.Spawn(42); // spawn object with any args 88 | 89 | // do something 90 | 91 | pool.Return(bar); 92 | pool.Return(baz); 93 | */ 94 | 95 | #ifndef SMART_POOL_H 96 | #define SMART_POOL_H 97 | 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | 106 | // Use large enough in types to hold stamps and indices 107 | // uint64_t will overflows in about 370 years if you will increase it by 3 400 000 000 (3.4 GHz) 108 | // every second. In normal cases there is eternity needed to overflow uint64_t 109 | using PoolIndex = uint64_t; 110 | using PoolStamp = uint64_t; 111 | 112 | enum EPoolStamp 113 | { 114 | PoolStamp_NotConstructed, 115 | PoolStamp_Free, 116 | PoolStamp_Origin 117 | }; 118 | 119 | template 120 | class Pool; 121 | 122 | /////////////////////////////////////////////////////////////////////////////////////// 123 | /// 124 | /////////////////////////////////////////////////////////////////////////////////////// 125 | template 126 | class PoolHandle final 127 | { 128 | public: 129 | /////////////////////////////////////////////////////////////////////////////////////// 130 | /// Default contructor. Create "invalid" pool handle 131 | /////////////////////////////////////////////////////////////////////////////////////// 132 | PoolHandle() : mIndex(0), mStamp(PoolStamp_Free) { } 133 | private: 134 | friend class Pool; 135 | PoolIndex mIndex; 136 | PoolStamp mStamp; 137 | 138 | /////////////////////////////////////////////////////////////////////////////////////// 139 | /// Private constructor for pool needs 140 | /////////////////////////////////////////////////////////////////////////////////////// 141 | PoolHandle(PoolIndex index, PoolStamp stamp) : mIndex(index), mStamp(stamp) { } 142 | }; 143 | 144 | /////////////////////////////////////////////////////////////////////////////////////// 145 | /// Internal class for holding user objects. Stores additional information along with 146 | /// user's object 147 | /////////////////////////////////////////////////////////////////////////////////////// 148 | template 149 | class PoolRecord final 150 | { 151 | public: 152 | /////////////////////////////////////////////////////////////////////////////////////// 153 | /// Constructor with arguments delegation 154 | /////////////////////////////////////////////////////////////////////////////////////// 155 | template 156 | PoolRecord(PoolStamp stamp, Args &&... args) : mStamp(stamp), mObject(args...) { } 157 | 158 | /////////////////////////////////////////////////////////////////////////////////////// 159 | /// 160 | /////////////////////////////////////////////////////////////////////////////////////// 161 | PoolRecord() : mStamp(PoolStamp_Free) { } 162 | private: 163 | friend class Pool; 164 | PoolStamp mStamp; 165 | T mObject; 166 | }; 167 | 168 | /////////////////////////////////////////////////////////////////////////////////////// 169 | /// Use this class as base to be able to obtain pointer to parent pool in derived class 170 | /////////////////////////////////////////////////////////////////////////////////////// 171 | template 172 | class Poolable 173 | { 174 | public: 175 | Pool* ParentPool() 176 | { 177 | return mOwner; 178 | } 179 | private: 180 | friend class Pool; 181 | Pool* mOwner { nullptr }; 182 | }; 183 | 184 | /////////////////////////////////////////////////////////////////////////////////////// 185 | /// See description in the beginning of this file 186 | /////////////////////////////////////////////////////////////////////////////////////// 187 | template 188 | class Pool final 189 | { 190 | public: 191 | typedef void* (*MemoryAllocFunc)(size_t size); 192 | typedef void (*MemoryFreeFunc)(void* ptr); 193 | 194 | /////////////////////////////////////////////////////////////////////////////////////// 195 | /// @param baseSize - base count of preallocated objects in the pool 196 | /// @param memoryAlloc Memory allocation function. malloc by default 197 | /// @param memoryFree Memory deallocation function. free by default 198 | /////////////////////////////////////////////////////////////////////////////////////// 199 | Pool(size_t baseSize, MemoryAllocFunc memoryAlloc = malloc, MemoryFreeFunc memoryFree = free) 200 | : mCapacity(baseSize) 201 | , MemoryAlloc(memoryAlloc) 202 | , MemoryFree(memoryFree) 203 | { 204 | AllocMemory(); 205 | for (PoolIndex i = 0; i < baseSize; ++i) 206 | { 207 | mFreeQueue.push(i); 208 | } 209 | } 210 | 211 | /////////////////////////////////////////////////////////////////////////////////////// 212 | /// Destructor 213 | /////////////////////////////////////////////////////////////////////////////////////// 214 | ~Pool() 215 | { 216 | Clear(); 217 | } 218 | 219 | /////////////////////////////////////////////////////////////////////////////////////// 220 | /// Returns handle to free object, or if pool is full allocates new object and returns 221 | /// handle to it. 222 | /// 223 | /// Throws std::bad_alloc when unable to allocate memory. 224 | /////////////////////////////////////////////////////////////////////////////////////// 225 | template 226 | PoolHandle Spawn(Args &&... args) 227 | { 228 | if (mFreeQueue.empty()) 229 | { 230 | // No free objects, grow pool 231 | const auto oldCapacity = mCapacity; 232 | if (mCapacity == 0) 233 | { 234 | mCapacity = 1; 235 | } 236 | else 237 | { 238 | mCapacity = static_cast(ceil(mCapacity * GrowRate)); 239 | } 240 | // Remember old records 241 | const auto old = mRecords; 242 | // Create new array with larger size 243 | AllocMemory(); 244 | // Register new free indices 245 | for (PoolIndex i = oldCapacity; i < mCapacity; ++i) 246 | { 247 | mFreeQueue.push(i); 248 | } 249 | for (size_t i = 0; i < oldCapacity; ++i) 250 | { 251 | if (old[i].mStamp != PoolStamp_NotConstructed) 252 | { 253 | // Try to invoke move contructor and fallback to copy contructor if no move 254 | // constructor is presented. 255 | new (&mRecords[i]) PoolRecord(std::move(old[i])); 256 | } 257 | } 258 | // Remove old array of records 259 | DestroyObjects(old, oldCapacity); 260 | } 261 | const auto index = GetFreeIndex(); 262 | auto &rec = mRecords[index]; 263 | // Reconstruct record via placement new. 264 | auto element = new (&rec) PoolRecord(MakeStamp(), args...); 265 | // For newly constructed object we have to check if it is derived from Poolable 266 | // and set pointer to this pool as owner. 267 | if (std::is_base_of, T>::value) 268 | { 269 | ((Poolable*)&element->mObject)->mOwner = this; 270 | } 271 | ++mSpawnedCount; 272 | // Return handle to existing object. 273 | return PoolHandle{index, rec.mStamp}; 274 | } 275 | 276 | /////////////////////////////////////////////////////////////////////////////////////// 277 | /// Will return object with 'handle' to the pool 278 | /// Calls destructor of returnable object 279 | /////////////////////////////////////////////////////////////////////////////////////// 280 | void Return(const PoolHandle &handle) 281 | { 282 | assert(handle.mIndex < mCapacity); 283 | auto &rec = mRecords[handle.mIndex]; 284 | if (rec.mStamp != PoolStamp_Free) 285 | { 286 | rec.mStamp = PoolStamp_Free; 287 | // Destruct 288 | rec.mObject.~T(); 289 | --mSpawnedCount; 290 | // Register handle's index as free 291 | mFreeQueue.push(handle.mIndex); 292 | } 293 | } 294 | 295 | /////////////////////////////////////////////////////////////////////////////////////// 296 | /// Destructs every object in pool, frees memory block that holds records. All refs 297 | /// will become invalid! 298 | /////////////////////////////////////////////////////////////////////////////////////// 299 | void Clear() 300 | { 301 | DestroyObjects(mRecords, mCapacity); 302 | // Clear free indices queue 303 | mFreeQueue = std::queue(); 304 | mRecords = nullptr; 305 | mCapacity = 0; 306 | mGlobalStamp = PoolStamp_Origin; 307 | mSpawnedCount = 0; 308 | } 309 | 310 | /////////////////////////////////////////////////////////////////////////////////////// 311 | /// Returns true if 'handle' corresponds to object, that handle indexes. 312 | /////////////////////////////////////////////////////////////////////////////////////// 313 | bool IsValid(const PoolHandle &handle) noexcept 314 | { 315 | assert(handle.mIndex < mCapacity); 316 | return handle.mStamp == mRecords[handle.mIndex].mStamp; 317 | } 318 | 319 | /////////////////////////////////////////////////////////////////////////////////////// 320 | /// Returns reference to object by its handle 321 | /// WARNING: You should check handle to validity thru IsValid before pass it to method, 322 | /// otherwise you might get wrong object (destructed, or object that already in use by 323 | /// someone other), or even segfault if you pass handle with index out of bounds. 324 | /// Checking handle is similar as checking pointer for "non-nullptr" before use it. 325 | /////////////////////////////////////////////////////////////////////////////////////// 326 | T &At(const PoolHandle &handle) const 327 | { 328 | assert(handle.mIndex < mCapacity); 329 | return mRecords[handle.mIndex].mObject; 330 | } 331 | 332 | /////////////////////////////////////////////////////////////////////////////////////// 333 | /// Same as At. 334 | /////////////////////////////////////////////////////////////////////////////////////// 335 | T &operator[](const PoolHandle &handle) const 336 | { 337 | return At(handle); 338 | } 339 | 340 | /////////////////////////////////////////////////////////////////////////////////////// 341 | /// Returns count of objects that are already spawned. 342 | /////////////////////////////////////////////////////////////////////////////////////// 343 | size_t GetSpawnedCount() const noexcept 344 | { 345 | return mSpawnedCount; 346 | } 347 | 348 | /////////////////////////////////////////////////////////////////////////////////////// 349 | /// Returns total capacity of this pool. 350 | /////////////////////////////////////////////////////////////////////////////////////// 351 | size_t GetCapacity() const noexcept 352 | { 353 | return mCapacity; 354 | } 355 | 356 | /////////////////////////////////////////////////////////////////////////////////////// 357 | /// Returns pointer to memory block that holds every record of this pool. 358 | /// You should NEVER store returned pointer: its address may change by calling Spawn. 359 | /////////////////////////////////////////////////////////////////////////////////////// 360 | PoolRecord *GetRecords() noexcept 361 | { 362 | return mRecords; 363 | } 364 | 365 | /////////////////////////////////////////////////////////////////////////////////////// 366 | /// begin method for "range-based for". 367 | /////////////////////////////////////////////////////////////////////////////////////// 368 | PoolRecord *begin() 369 | { 370 | return mRecords; 371 | } 372 | 373 | /////////////////////////////////////////////////////////////////////////////////////// 374 | /// end method for "range-based for". 375 | /////////////////////////////////////////////////////////////////////////////////////// 376 | PoolRecord *end() 377 | { 378 | return mRecords + mCapacity - 1; 379 | } 380 | 381 | /////////////////////////////////////////////////////////////////////////////////////// 382 | /// Use this only to obtain handle by 'this' pointer inside class method. 383 | /// Note: Do not rely on pointers to objects in pool, they can suddenly become 384 | /// invalid. 'this' pointer can be used, because it is always valid. 385 | /////////////////////////////////////////////////////////////////////////////////////// 386 | PoolHandle HandleByPointer(T * const ptr) 387 | { 388 | // Pointer must be in bounds 389 | if (ptr < &mRecords[0].mObject || ptr >= &mRecords[mCapacity - 1].mObject) 390 | { 391 | return PoolHandle(); 392 | } 393 | 394 | // Offset ptr to get record 395 | const auto record = reinterpret_cast *>( 396 | reinterpret_cast(ptr) - sizeof(PoolStamp)); 397 | 398 | const auto p0 = reinterpret_cast(&mRecords[0]); 399 | const auto pn = reinterpret_cast(record); 400 | 401 | // Calculate index 402 | const intptr_t distance = pn - p0; 403 | const intptr_t index = distance / sizeof(PoolRecord); 404 | 405 | return PoolHandle(index, record->mStamp); 406 | } 407 | private: 408 | friend class PoolHandle; 409 | 410 | /////////////////////////////////////////////////////////////////////////////////////// 411 | /// Returns unique global stamp 412 | /////////////////////////////////////////////////////////////////////////////////////// 413 | PoolStamp MakeStamp() noexcept 414 | { 415 | return mGlobalStamp++; 416 | } 417 | 418 | /////////////////////////////////////////////////////////////////////////////////////// 419 | /// Calls destructors for every spawned object and then frees memory block 420 | /////////////////////////////////////////////////////////////////////////////////////// 421 | void DestroyObjects(PoolRecord *ptr, size_t count) 422 | { 423 | for (size_t i = 0; i < count; ++i) 424 | { 425 | // Destruct only busy objects, not the free ones (they are already destructed) 426 | if (ptr[i].mStamp != PoolStamp_Free && ptr[i].mStamp != PoolStamp_NotConstructed) 427 | { 428 | ptr[i].mObject.~T(); 429 | } 430 | } 431 | MemoryFree(ptr); 432 | } 433 | 434 | /////////////////////////////////////////////////////////////////////////////////////// 435 | /// Allocates new memory block 436 | /////////////////////////////////////////////////////////////////////////////////////// 437 | void AllocMemory() 438 | { 439 | const size_t sizeBytes = sizeof(PoolRecord) * mCapacity; 440 | mRecords = reinterpret_cast *>(MemoryAlloc(sizeBytes)); 441 | if (!mRecords) 442 | { 443 | throw std::bad_alloc(); 444 | } 445 | memset(mRecords, PoolStamp_NotConstructed, sizeBytes); 446 | } 447 | 448 | /////////////////////////////////////////////////////////////////////////////////////// 449 | /// Returns index of next free record 450 | /////////////////////////////////////////////////////////////////////////////////////// 451 | PoolIndex GetFreeIndex() 452 | { 453 | const auto index = mFreeQueue.front(); 454 | mFreeQueue.pop(); 455 | return index; 456 | } 457 | 458 | /////////////////////////////////////////////////////////////////////////////////////// 459 | // Internals 460 | /////////////////////////////////////////////////////////////////////////////////////// 461 | 462 | /// By default, grow rate is golden ratio 463 | static constexpr float GrowRate = 1.618f; 464 | static_assert(GrowRate > 1.0f, "Grow rate must be greater than 1"); 465 | 466 | size_t mSpawnedCount { 0 }; 467 | PoolStamp mGlobalStamp { PoolStamp_Origin }; 468 | PoolRecord *mRecords { nullptr }; 469 | size_t mCapacity { 0 }; 470 | std::queue mFreeQueue; 471 | MemoryAllocFunc MemoryAlloc; 472 | MemoryFreeFunc MemoryFree; 473 | }; 474 | 475 | #endif -------------------------------------------------------------------------------- /Tests.cpp: -------------------------------------------------------------------------------- 1 | #include "Pool.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define ONLY_POOL_TESTS 0 11 | 12 | using namespace std; 13 | 14 | // Performance test 15 | constexpr int ObjectCountPerTest = 2000000; 16 | 17 | class Vec3 18 | { 19 | public: 20 | float x, y, z; 21 | Vec3() 22 | { 23 | } 24 | Vec3(float x, float y, float z) : x(x), y(y), z(z) 25 | { 26 | } 27 | float Length() const 28 | { 29 | return sqrt(x * x + y * y + z * z); 30 | } 31 | }; 32 | 33 | class Quat 34 | { 35 | public: 36 | float x, y, z, w; 37 | 38 | Quat() 39 | { 40 | } 41 | Quat(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) 42 | { 43 | } 44 | Quat(const Vec3 &axis, float angle) 45 | { 46 | float halfAngle = angle * 0.5f; 47 | float d = axis.Length(); 48 | float s = sin(halfAngle) / d; 49 | x = axis.x * s; 50 | y = axis.y * s; 51 | z = axis.z * s; 52 | w = cos(halfAngle); 53 | } 54 | float SqrLength() const 55 | { 56 | return x * x + y * y + z * z + w * w; 57 | } 58 | }; 59 | 60 | class Matrix 61 | { 62 | public: 63 | float mElements[16]; 64 | Matrix() 65 | { 66 | } 67 | Matrix(const Quat &rotation, const Vec3 &scale, const Vec3 &translation) 68 | { 69 | FromRotationScaleTranslation(rotation, scale, translation); 70 | } 71 | void FromRotationScaleTranslation( 72 | const Quat &rotation, const Vec3 &scale, const Vec3 &translation) 73 | { 74 | float s = 2.0f / rotation.SqrLength(); 75 | float xs = rotation.x * s, ys = rotation.y * s, zs = rotation.z * s; 76 | float wx = rotation.w * xs, wy = rotation.w * ys, wz = rotation.w * zs; 77 | float xx = rotation.x * xs, xy = rotation.x * ys, xz = rotation.x * zs; 78 | float yy = rotation.y * ys, yz = rotation.y * zs, zz = rotation.z * zs; 79 | mElements[0] = 1.0f - (yy + zz); 80 | mElements[1] = xy + wz; 81 | mElements[2] = xz - wy; 82 | mElements[3] = 0.0f; 83 | mElements[4] = xy - wz; 84 | mElements[5] = 1.0f - (xx + zz); 85 | mElements[6] = yz + wx; 86 | mElements[7] = 0.0f; 87 | mElements[8] = xz + wy; 88 | mElements[9] = yz - wx; 89 | mElements[10] = 1.0f - (xx + yy); 90 | mElements[11] = 0.0f; 91 | mElements[12] = translation.x; 92 | mElements[13] = translation.y; 93 | mElements[14] = translation.z; 94 | mElements[15] = 1.0f; 95 | } 96 | Matrix operator*(const Matrix &other) const 97 | { 98 | Matrix out; 99 | float *temp = out.mElements; 100 | const float *a = mElements; 101 | const float *b = other.mElements; 102 | temp[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; 103 | temp[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; 104 | temp[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; 105 | temp[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; 106 | temp[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; 107 | temp[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; 108 | temp[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; 109 | temp[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; 110 | temp[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; 111 | temp[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; 112 | temp[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; 113 | temp[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; 114 | temp[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; 115 | temp[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; 116 | temp[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; 117 | temp[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; 118 | return out; 119 | } 120 | Matrix &operator*=(const Matrix &other) 121 | { 122 | *this = *this * other; 123 | return *this; 124 | } 125 | }; 126 | 127 | class PoolableNode : public Poolable 128 | { 129 | public: 130 | string mName { "Unnamed" }; 131 | PoolHandle mParent; 132 | vector> mChildren; 133 | Matrix mLocalTransform; 134 | Matrix mGlobalTransform; 135 | Vec3 mPosition { 0, 0, 0 }; 136 | Vec3 mScale { 1, 1, 1 }; 137 | Quat mRotation { 0, 0, 0, 1 }; 138 | bool mNeedUpdate { true }; 139 | public: 140 | PoolableNode() 141 | { 142 | 143 | } 144 | virtual ~PoolableNode() 145 | { 146 | for (auto &child : mChildren) 147 | { 148 | ParentPool()->Return(child); 149 | } 150 | } 151 | void SetName(const string &name) 152 | { 153 | mName = name; 154 | } 155 | void AttachTo(const PoolHandle &parentHandle) 156 | { 157 | mParent = parentHandle; 158 | auto self = ParentPool()->HandleByPointer(this); 159 | ParentPool()->At(parentHandle).mChildren.push_back(self); 160 | } 161 | void Update() 162 | { 163 | if (mNeedUpdate) 164 | { 165 | mLocalTransform.FromRotationScaleTranslation(mRotation, mScale, mPosition); 166 | 167 | if (ParentPool()->IsValid(mParent)) 168 | { 169 | mGlobalTransform = ParentPool()->At(mParent).GetGlobalTransform() * mLocalTransform; 170 | } 171 | else 172 | { 173 | mGlobalTransform = mLocalTransform; 174 | } 175 | 176 | mNeedUpdate = false; 177 | 178 | for (const auto &child : mChildren) 179 | { 180 | ParentPool()->At(child).Update(); 181 | } 182 | } 183 | } 184 | void SetPosition(const Vec3 &p) 185 | { 186 | mPosition = p; 187 | mNeedUpdate = true; 188 | } 189 | const Vec3 &GetPosition() const 190 | { 191 | return mPosition; 192 | } 193 | void SetRotation(const Quat &q) 194 | { 195 | mRotation = q; 196 | mNeedUpdate = true; 197 | } 198 | const Quat &GetRotation() const 199 | { 200 | return mRotation; 201 | } 202 | void SetScale(const Vec3 &scale) 203 | { 204 | mScale = scale; 205 | mNeedUpdate = true; 206 | } 207 | const Vec3 &GetScale() const 208 | { 209 | return mScale; 210 | } 211 | const Matrix &GetLocalTransform() 212 | { 213 | Update(); 214 | return mLocalTransform; 215 | } 216 | const Matrix &GetGlobalTransform() 217 | { 218 | Update(); 219 | return mGlobalTransform; 220 | } 221 | }; 222 | 223 | class OrdinaryNode : public enable_shared_from_this 224 | { 225 | private: 226 | string mName { "Unnamed" }; 227 | weak_ptr mParent; 228 | vector> mChildren; 229 | Matrix mLocalTransform; 230 | Matrix mGlobalTransform; 231 | Vec3 mPosition { 0, 0, 0 }; 232 | Vec3 mScale { 1, 1, 1 }; 233 | Quat mRotation { 0, 0, 0, 1 }; 234 | bool mNeedUpdate { true }; 235 | 236 | public: 237 | OrdinaryNode() 238 | { 239 | 240 | } 241 | virtual ~OrdinaryNode() 242 | { 243 | } 244 | void SetName(const string &name) 245 | { 246 | mName = name; 247 | } 248 | void AttachTo(const shared_ptr &parent) 249 | { 250 | parent->mChildren.push_back(shared_from_this()); 251 | mParent = parent; 252 | } 253 | }; 254 | 255 | void RunSanityTests() 256 | { 257 | cout << endl << endl; 258 | cout << "Running sanity tests" << endl; 259 | { 260 | Pool pool(1); 261 | auto a = pool.Spawn(); 262 | pool[a].SetName("A"); 263 | auto b = pool.Spawn(); 264 | pool[b].SetName("B"); 265 | auto c = pool.Spawn(); 266 | pool[c].SetName("C"); 267 | pool.Return(a); 268 | pool.Return(b); 269 | pool.Return(c); 270 | a = pool.Spawn(); 271 | pool[a].SetName("A"); 272 | } 273 | 274 | { 275 | Pool pool(1024); 276 | 277 | auto handle = pool.Spawn(); 278 | assert(pool.IsValid(handle)); 279 | pool.Return(handle); 280 | 281 | assert(!pool.IsValid(handle)); 282 | assert(pool.GetSpawnedCount() == 0); 283 | 284 | auto newHandle = pool.Spawn(); 285 | assert(pool.IsValid(newHandle)); 286 | assert(pool.GetSpawnedCount() == 1); 287 | pool.Return(newHandle); 288 | } 289 | 290 | cout << "Passed" << endl; 291 | } 292 | 293 | void RunRandomObjectPerformanceTest() 294 | { 295 | cout << endl << endl; 296 | cout << "Running random object performance test" << endl; 297 | cout << "Object count: " << ObjectCountPerTest << endl; 298 | 299 | // PoolableNode 300 | { 301 | Pool pool(1024); 302 | 303 | auto lastTime = chrono::high_resolution_clock::now(); 304 | for (int i = 0; i < ObjectCountPerTest; ++i) 305 | { 306 | auto parent = pool.Spawn(); 307 | pool[parent].SetName("Parent"); 308 | auto child = pool.Spawn(); 309 | pool[child].SetName("Child"); 310 | pool[child].AttachTo(parent); 311 | pool.Return(parent); 312 | } 313 | 314 | cout << "Pool: " 315 | << chrono::duration_cast( 316 | chrono::high_resolution_clock::now() - lastTime) 317 | .count() 318 | << " microseconds" << endl; 319 | // cout << "Pool size: " << pool.GetSize() << endl; 320 | } 321 | 322 | #if !ONLY_POOL_TESTS 323 | // OrdinaryNode 324 | { 325 | auto lastTime = chrono::high_resolution_clock::now(); 326 | 327 | for (int i = 0; i < ObjectCountPerTest; ++i) 328 | { 329 | auto parent = make_shared(); 330 | parent->SetName("Parent"); 331 | auto child = make_shared(); 332 | child->SetName("Child"); 333 | child->AttachTo(parent); 334 | parent.reset(); 335 | } 336 | 337 | cout << "shared_ptr: " 338 | << chrono::duration_cast( 339 | chrono::high_resolution_clock::now() - lastTime) 340 | .count() 341 | << " microseconds" << endl; 342 | } 343 | #endif 344 | cout << "Passed" << endl; 345 | } 346 | 347 | void RunHugeAmountOfObjectsPerformanceTest() 348 | { 349 | class Foo 350 | { 351 | private: 352 | int mBar; 353 | string mBaz; 354 | 355 | public: 356 | Foo() 357 | { 358 | } 359 | Foo(int bar) : mBar(bar) 360 | { 361 | } 362 | }; 363 | 364 | 365 | 366 | cout << endl << endl; 367 | cout << "Running huge amount of objects performance test" << endl; 368 | cout << "Object count: " << ObjectCountPerTest << endl; 369 | 370 | { 371 | auto lastTime = chrono::high_resolution_clock::now(); 372 | 373 | vector> handles; 374 | handles.reserve(ObjectCountPerTest); 375 | Pool pool(ObjectCountPerTest); 376 | 377 | for (int i = 0; i < ObjectCountPerTest; ++i) 378 | { 379 | handles.push_back(pool.Spawn(1234)); 380 | } 381 | 382 | for (const auto &handle : handles) 383 | { 384 | pool.Return(handle); 385 | } 386 | 387 | cout << "Pool: " 388 | << chrono::duration_cast( 389 | chrono::high_resolution_clock::now() - lastTime) 390 | .count() 391 | << " microseconds" << endl; 392 | } 393 | 394 | #if !ONLY_POOL_TESTS 395 | { 396 | auto lastTime = chrono::high_resolution_clock::now(); 397 | 398 | vector> pointers; 399 | pointers.reserve(ObjectCountPerTest); 400 | for (int i = 0; i < ObjectCountPerTest; ++i) 401 | { 402 | pointers.push_back(make_unique(1234)); 403 | } 404 | 405 | for (auto &ptr : pointers) 406 | { 407 | ptr.reset(); 408 | } 409 | 410 | cout << "unique_ptr: " 411 | << chrono::duration_cast( 412 | chrono::high_resolution_clock::now() - lastTime) 413 | .count() 414 | << " microseconds" << endl; 415 | } 416 | #endif 417 | cout << "Passed" << endl; 418 | } 419 | 420 | void RunDataLocalityPerformanceTest() 421 | { 422 | class Foo 423 | { 424 | private: 425 | Matrix mA; 426 | Matrix mB; 427 | Matrix mResult; 428 | public: 429 | Foo() 430 | { 431 | mA.FromRotationScaleTranslation({ 0, 0, 0, 1 }, { 1, 1, 1 }, { 1, 0, 0 }); 432 | mB.FromRotationScaleTranslation({ 1, 0, 0, 1 }, { 1, 1, 1 }, { 1, 1, 0 }); 433 | } 434 | 435 | void Calculate() 436 | { 437 | mResult = mA * mB; 438 | } 439 | }; 440 | 441 | // Performance test 442 | constexpr int iterCount = 20; 443 | 444 | cout << endl << endl; 445 | cout << "Running data locality performance test" << endl; 446 | cout << "Object count: " << ObjectCountPerTest << endl; 447 | 448 | long long totalTime = 0; 449 | 450 | #if !ONLY_POOL_TESTS 451 | { 452 | vector> pointers; 453 | pointers.reserve(ObjectCountPerTest); 454 | for (int i = 0; i < ObjectCountPerTest; ++i) 455 | { 456 | pointers.push_back(make_unique()); 457 | } 458 | 459 | for (int k = 0; k < iterCount; ++k) 460 | { 461 | auto lastTime = chrono::high_resolution_clock::now(); 462 | for (const auto &ptr : pointers) 463 | { 464 | ptr->Calculate(); 465 | } 466 | 467 | totalTime += chrono::duration_cast( 468 | chrono::high_resolution_clock::now() - lastTime) 469 | .count(); 470 | } 471 | 472 | for (auto &ptr : pointers) 473 | { 474 | ptr.reset(); 475 | } 476 | } 477 | 478 | cout << "unique_ptr: " << totalTime / iterCount << " microseconds" << endl; 479 | #endif 480 | 481 | 482 | 483 | { 484 | vector> handles; 485 | handles.reserve(ObjectCountPerTest); 486 | Pool pool(ObjectCountPerTest); 487 | 488 | for (int i = 0; i < ObjectCountPerTest; ++i) 489 | { 490 | handles.push_back(pool.Spawn()); 491 | } 492 | 493 | totalTime = 0; 494 | for (int k = 0; k < iterCount; ++k) 495 | { 496 | auto lastTime = chrono::high_resolution_clock::now(); 497 | for (const auto &handle : handles) 498 | { 499 | pool.At(handle).Calculate(); 500 | } 501 | 502 | totalTime += chrono::duration_cast( 503 | chrono::high_resolution_clock::now() - lastTime) 504 | .count(); 505 | } 506 | 507 | for (const auto &handle : handles) 508 | { 509 | pool.Return(handle); 510 | } 511 | } 512 | 513 | cout << "Pool: " << totalTime / iterCount << " microseconds" << endl; 514 | 515 | cout << "Passed" << endl; 516 | } 517 | 518 | int main(int argc, char **argv) 519 | { 520 | RunDataLocalityPerformanceTest(); 521 | RunSanityTests(); 522 | RunRandomObjectPerformanceTest(); 523 | RunHugeAmountOfObjectsPerformanceTest(); 524 | 525 | system("pause"); 526 | return 0; 527 | } 528 | -------------------------------------------------------------------------------- /_clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | SortIncludes: false 3 | AllowShortFunctionsOnASingleLine: false 4 | AlignAfterOpenBracket: DontAlign -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SmartPool 2 | 3 | SmartPool is an object pool single-header library for fast object allocation. All objects inside of a pool placed in a contiguous memory block to be cache-friendly. 4 | 5 | ## Overview 6 | 7 | Pool serves for a few goals: eliminates unnecessary memory allocations, preserves data locality, gives mechanism of control for object indices. 8 | 9 | This pool provides user the easy way of control for spawned objects through special handles. Why we can't use ordinary pointers? Answer is simple: when pool grows, its memory block may change its position in memory and all pointers will become invalid. To avoid this problem, we use handles. 10 | 11 | What is "handle"? Handle is something like index, but with additional information, that allows us to ensure that handle "points" to same object as before. This additional info called "stamp". When you asks pool for a new object, pool marks it with unique "stamp" and gives you handle with index of a new object and "stamp" of an object. Now if you want to ensure, that handle "points" to same object as before, you just compare "stamps" - if they are same, then handle is correct (you can check handle validity using IsValid method). 12 | 13 | Pool implementation is object-agnostic, so you can store any suitable object in it. It also may contain non-POD objects. 14 | 15 | ## Installation 16 | 17 | SmartPool requires C++11-compliant compiler. To use pool, just copy Pool.h to your source directory. 18 | 19 | ## Notes 20 | Your object should have constructor without arguments. 21 | 22 | Create pool with large enough capacity, because any Spawn method called on full pool will result in memory reallocation and memory movement, which is quite expensive operations. 23 | 24 | You can pass your own memory allocation/deallocation functions as 2nd and 3rd parameters in Pool constructor. 25 | 26 | If you need to use pool from any class that is stored in the pool, inherit your class from Poolable like this: 27 | ```c++ 28 | class Foo : public Poolable { 29 | ... 30 | }; 31 | ``` 32 | Now when you need to access owning pool, call ParentPool() 33 | ```c++ 34 | class Foo : public Poolable { 35 | PoolHandle mSomeHandle; 36 | void DoSomething() { 37 | Pool* pool = ParentPool(); 38 | 39 | if(pool->IsValid(mSomeHandle)) { 40 | auto & object = pool->At(mSomeHandle); 41 | } 42 | } 43 | }; 44 | ``` 45 | Why this works? Inside of Spawn method, pool checks if stored class have base of Poolable, and if it does, just sets some internal variable of Poolable. 46 | 47 | ## Examples 48 | 49 | ```c++ 50 | class Foo { 51 | private: 52 | int fooBar; 53 | public: 54 | Foo() {} 55 | Foo(int foobar) : fooBar(foobar) {} 56 | }; 57 | 58 | ... 59 | 60 | Pool pool(1024); // make pool with default capacity of 1024 objects 61 | PoolHandle bar = pool.Spawn(); // spawn object with default constructor 62 | PoolHandle baz = pool.Spawn(42); // spawn object with any args 63 | 64 | // do something 65 | 66 | // return object to pool 67 | pool.Return(bar); 68 | pool.Return(baz); 69 | 70 | ... 71 | 72 | // somewhere in the code 73 | 74 | // check handle for validity 75 | if(pool.IsValid(baz)) { 76 | cout << "Baz is valid" << endl; 77 | } else { 78 | cout << "Baz is invalid" << endl; 79 | } 80 | 81 | ``` 82 | 83 | ## How it works 84 | Firstly, pool allocates memory block with initial size = initialCapacity * recordSize, fills it with special marks, which indicates that memory piece is unused. Also pool creates list of indices of free memory blocks. 85 | 86 | When user calls Spawn method, pool pops index of free memory block, constructs object in it using placement new, makes new stamp and returns handle to the user. 87 | 88 | When user calls Return method, pool returns index of the object to "free list", marks object as free and calls destructor of the object. 89 | 90 | When pool is destroyed, it calls destructors of all "busy" objects inside of it. So there is no memory leaks in the end. 91 | 92 | ## Q&A 93 | 94 | **Q**: How fast is Spawn method? 95 | **A**: Spawn method has amortized complexity of O(1). 96 | 97 | **Q**: How fast is Return method? 98 | **A**: Return method has amortized complexity of O(1). 99 | 100 | ## Tests 101 | 102 | Tests (sanity and performance) are all in Tests.cpp. 103 | 104 | ## License 105 | 106 | The MIT License 107 | 108 | Copyright (c) 2017-2018 Stepanov Dmitriy a.k.a mr.DIMAS a.k.a v1al 109 | 110 | Permission is hereby granted, free of charge, to any person obtaining a copy 111 | of this software and associated documentation files (the "Software"), to deal 112 | in the Software without restriction, including without limitation the rights 113 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 114 | copies of the Software, and to permit persons to whom the Software is 115 | furnished to do so, subject to the following conditions: 116 | 117 | The above copyright notice and this permission notice shall be included in 118 | all copies or substantial portions of the Software. 119 | 120 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 121 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 122 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 123 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 124 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 125 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 126 | THE SOFTWARE. 127 | --------------------------------------------------------------------------------