2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/common/fixed-size-memory-pool.h>
22 #include <dali/devel-api/threading/mutex.h>
23 #include <dali/public-api/common/dali-common.h>
24 #include <dali/public-api/common/vector-wrapper.h>
32 * @brief Private implementation class
34 struct FixedSizeMemoryPool::Impl
38 * @brief Struct to represent a block of memory from which allocations can be made.
40 * The block forms a linked list.
44 void* blockMemory; ///< The allocated memory from which allocations can be made
45 Block* nextBlock; ///< The next block in the linked list
47 KeyType mIndexOffset; ///< The Offset of this block's index
49 SizeType mBlockSize; ///< Size of the block in bytes
52 * @brief Construct a new block with given size
54 * @param size The size of the memory block to allocate in bytes. Must be non-zero.
63 blockMemory = ::operator new(size);
64 DALI_ASSERT_ALWAYS(blockMemory && "Out of memory");
72 ::operator delete(blockMemory);
77 Block(const Block& block);
80 Block& operator=(const Block& block);
86 Impl(SizeType fixedSize, SizeType initialCapacity, SizeType maximumBlockCapacity, SizeType maximumBlockCount)
88 mFixedSize(fixedSize),
89 mMemoryBlocks(initialCapacity * mFixedSize),
90 mMaximumBlockCapacity(maximumBlockCapacity),
91 mCurrentBlock(&mMemoryBlocks),
92 mCurrentBlockCapacity(initialCapacity),
94 mMaximumBlockCount(maximumBlockCount),
95 mDeletedObjects(nullptr)
97 // We need enough room to store the deleted list in the data
98 DALI_ASSERT_DEBUG(mFixedSize >= sizeof(void*));
100 #if defined(__LP64__)
101 if(mMaximumBlockCount < 0xffffffff)
103 // Only use mBlocks for key/ptr conversion with max number of blocks
105 mBlocks.push_back(&mMemoryBlocks);
107 // Compute masks and shifts
108 int bitCount = (logf(mMaximumBlockCount) / logf(2.0f)) + 1;
109 mBlockShift = 32 - bitCount;
110 mBlockIdMask = ((0x01 << bitCount) - 1) << mBlockShift;
111 mIndexMask = ~mBlockIdMask;
125 * @brief Allocate a new block for allocating memory from
127 void AllocateNewBlock()
129 // Double capacity for the new block
130 SizeType size = mCurrentBlockCapacity * 2;
131 if(size > mMaximumBlockCapacity || size < mCurrentBlockCapacity) // Check for overflow of size type
133 size = mMaximumBlockCapacity;
136 mCurrentBlockCapacity = size;
139 Block* block = new Block(mCurrentBlockCapacity * mFixedSize);
141 #if defined(__LP64__)
142 if(mBlockShift) // Key contains block id & index within block
144 // Add to main list of blocks
145 mBlocks.push_back(block);
147 mCurrentBlock->nextBlock = block; // Add end of linked list
148 mCurrentBlock = block;
149 #if defined(__LP64__)
153 // Heuristic optimization. Make bigger block positioned at front, instead of header.
154 // (Since change the Block value might be heavy)
155 if(mCurrentBlock == &mMemoryBlocks)
157 block->mIndexOffset = mMemoryBlocks.mBlockSize / mFixedSize;
159 // Add to end of linked list.
160 // Now mCurrentBlock is next block of header.
161 mCurrentBlock->nextBlock = block;
162 mCurrentBlock = block;
166 block->mIndexOffset = mCurrentBlock->mIndexOffset + (mCurrentBlock->mBlockSize / mFixedSize);
168 // Add new block as next of header.
169 mMemoryBlocks.nextBlock = block;
171 // Make previous mCurrentBlock as next of new block.
172 block->nextBlock = mCurrentBlock;
174 // Keep mCurrentBlock is next block of mMemoryBlocks.
175 mCurrentBlock = block;
180 mCurrentBlockSize = 0;
184 * @brief Release all newly added blocks, and remain only mMemoryBlocks which were allocated at constructor time.
186 void ResetMemoryBlock()
191 mCurrentBlock = &mMemoryBlocks;
192 mCurrentBlockCapacity = mMemoryBlocks.mBlockSize / mFixedSize;
193 mCurrentBlockSize = 0;
195 mMemoryBlocks.nextBlock = nullptr;
196 mDeletedObjects = nullptr;
198 #if defined(__LP64__)
199 mMemoryBlocks.mIndexOffset = 0;
200 if(mBlockShift) // Key contains block id & index within block
203 mBlocks.push_back(&mMemoryBlocks);
211 * @brief check the memory being free'd exists inside the memory pool
212 * @param[in] memory address of object to remove
214 void CheckMemoryIsInsidePool(const void* const memory)
216 bool inRange = false;
217 const Block* block = &mMemoryBlocks;
221 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
223 if((memory >= block->blockMemory) && (memory < (endOfBlock)))
228 block = block->nextBlock;
230 DALI_ASSERT_DEBUG(inRange && "Freeing memory that does not exist in memory pool");
236 * @brief Clean up memory block linked list instead of mMemoryBlocks. (mMemoryBlocks will be auto-destroyed by its destructor)
240 Block* block = mMemoryBlocks.nextBlock;
243 Block* nextBlock = block->nextBlock;
250 Mutex mMutex; ///< Mutex for thread-safe allocation and deallocation
252 SizeType mFixedSize; ///< The size of each allocation in bytes
254 Block mMemoryBlocks; ///< Linked list of allocated memory blocks
255 SizeType mMaximumBlockCapacity; ///< The maximum allowed capacity of allocations in a new memory block
257 #if defined(__LP64__)
258 std::vector<Block*> mBlocks; ///< Address of each allocated block
261 Block* mCurrentBlock; ///< Pointer to the active block
262 SizeType mCurrentBlockCapacity; ///< The maximum number of allocations that can be allocated for the current block
263 SizeType mCurrentBlockSize; ///< The number of allocations allocated to the current block
265 SizeType mMaximumBlockCount{0xffffffff}; ///< Maximum number of blocks (or unlimited)
266 #if defined(__LP64__)
267 SizeType mBlockShift{0x0}; ///< number of bits to shift block id in key
268 SizeType mBlockIdMask{0x0}; ///< mask for key conversion
269 SizeType mIndexMask{0xffffffff}; ///< mask for key conversion
272 void* mDeletedObjects; ///< Pointer to the head of the list of deleted objects. The addresses are stored in the allocated memory blocks.
275 FixedSizeMemoryPool::FixedSizeMemoryPool(
277 SizeType initialCapacity,
278 SizeType maximumBlockCapacity,
279 SizeType maximumBlockCount)
281 mImpl = std::make_unique<Impl>(fixedSize, initialCapacity, maximumBlockCapacity, maximumBlockCount);
284 FixedSizeMemoryPool::~FixedSizeMemoryPool() = default;
286 void* FixedSizeMemoryPool::Allocate()
288 // First, recycle deleted objects
289 if(mImpl->mDeletedObjects)
291 void* recycled = mImpl->mDeletedObjects;
292 mImpl->mDeletedObjects = *(reinterpret_cast<void**>(mImpl->mDeletedObjects)); // Pop head off front of deleted objects list
296 // Check if current block is full
297 if(mImpl->mCurrentBlockSize >= mImpl->mCurrentBlockCapacity)
299 mImpl->AllocateNewBlock();
302 // Placement new the object in block memory
303 uint8_t* objectAddress = static_cast<uint8_t*>(mImpl->mCurrentBlock->blockMemory);
304 objectAddress += mImpl->mCurrentBlockSize * mImpl->mFixedSize;
305 mImpl->mCurrentBlockSize++;
307 return objectAddress;
310 void FixedSizeMemoryPool::Free(void* memory)
315 mImpl->CheckMemoryIsInsidePool(memory);
318 // Add memory to head of deleted objects list. Store next address in the same memory space as the old object.
319 *(reinterpret_cast<void**>(memory)) = mImpl->mDeletedObjects;
320 mImpl->mDeletedObjects = memory;
324 void* FixedSizeMemoryPool::AllocateThreadSafe()
326 Mutex::ScopedLock lock(mImpl->mMutex);
330 void FixedSizeMemoryPool::FreeThreadSafe(void* memory)
334 Mutex::ScopedLock lock(mImpl->mMutex);
339 void* FixedSizeMemoryPool::GetPtrFromKey(FixedSizeMemoryPool::KeyType key)
341 #if defined(__LP64__)
342 uint32_t blockId{0u};
343 uint32_t index = key & mImpl->mIndexMask;
345 if(DALI_LIKELY(mImpl->mBlockShift)) // Key contains block id & index within block
347 blockId = (key & mImpl->mBlockIdMask) >> mImpl->mBlockShift;
348 if(blockId < mImpl->mBlocks.size())
350 return static_cast<uint8_t*>(mImpl->mBlocks[blockId]->blockMemory) + mImpl->mFixedSize * index;
356 // Treat as having no block id, and search for Nth item
357 Impl::Block* block = &mImpl->mMemoryBlocks;
358 while(block != nullptr)
360 const uint32_t indexOffset = block->mIndexOffset;
361 const SizeType numberOfItems = (block->mBlockSize) / mImpl->mFixedSize;
362 if(indexOffset <= index && index < indexOffset + numberOfItems)
364 return static_cast<uint8_t*>(block->blockMemory) + mImpl->mFixedSize * (index - indexOffset);
366 block = block->nextBlock;
371 // Treat ptrs as keys
372 return static_cast<void*>(key);
376 FixedSizeMemoryPool::KeyType FixedSizeMemoryPool::GetKeyFromPtr(void* ptr)
378 #if defined(__LP64__)
379 uint32_t blockId = 0;
383 // If block count is limited, the bit shift is non-zero.
384 if(DALI_LIKELY(mImpl->mBlockShift))
386 // Iterate backward so we can search at bigger blocks first.
387 blockId = mImpl->mBlocks.size();
388 for(auto iter = mImpl->mBlocks.rbegin(), iterEnd = mImpl->mBlocks.rend(); iter != iterEnd; ++iter)
393 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
395 if(block->blockMemory <= ptr && ptr < endOfBlock)
397 index = (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
405 // Block count is unlimited, key is item count. But, potentially have to iterate through many blocks.
406 Impl::Block* block = &mImpl->mMemoryBlocks;
407 while(block != nullptr && !found)
409 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
411 if(block->blockMemory <= ptr && ptr < endOfBlock)
413 index = block->mIndexOffset + (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
417 block = block->nextBlock;
422 return blockId << mImpl->mBlockShift | (index & mImpl->mIndexMask);
427 return static_cast<FixedSizeMemoryPool::KeyType>(ptr);
431 uint32_t FixedSizeMemoryPool::GetCapacity() const
433 // Ignores deleted objects list, just returns currently allocated size
434 uint32_t totalAllocation = 0;
436 Mutex::ScopedLock lock(mImpl->mMutex);
437 Impl::Block* block = &mImpl->mMemoryBlocks;
440 totalAllocation += block->mBlockSize;
441 block = block->nextBlock;
444 return totalAllocation;
447 void FixedSizeMemoryPool::ResetMemoryPool()
449 mImpl->ResetMemoryBlock();
452 } // namespace Internal