2 * Copyright (c) 2015 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
37 * @brief Struct to represent a block of memory from which allocations can be made.
39 * The block forms a linked list.
43 void* blockMemory; ///< The allocated memory from which allocations can be made
44 Block* nextBlock; ///< The next block in the linked list
45 SizeType mBlockSize; ///< Size of the block in bytes
48 * @brief Construct a new block with given size
50 * @param size The size of the memory block to allocate in bytes. Must be non-zero.
56 blockMemory = ::operator new(size);
57 DALI_ASSERT_ALWAYS(blockMemory && "Out of memory");
65 ::operator delete(blockMemory);
70 Block(const Block& block);
73 Block& operator=(const Block& block);
79 Impl(SizeType fixedSize, SizeType initialCapacity, SizeType maximumBlockCapacity, SizeType maximumBlockCount)
81 mFixedSize(fixedSize),
82 mMemoryBlocks(initialCapacity * mFixedSize),
83 mMaximumBlockCapacity(maximumBlockCapacity),
84 mCurrentBlock(&mMemoryBlocks),
85 mCurrentBlockCapacity(initialCapacity),
87 mMaximumBlockCount(maximumBlockCount),
88 mDeletedObjects(nullptr)
90 // We need enough room to store the deleted list in the data
91 DALI_ASSERT_DEBUG(mFixedSize >= sizeof(void*));
93 if(mMaximumBlockCount < 0xffffffff)
95 // Only use mBlocks for key/ptr conversion with max number of blocks
97 mBlocks.push_back(&mMemoryBlocks);
99 // Compute masks and shifts
100 int bitCount = (logf(mMaximumBlockCount) / logf(2.0f)) + 1;
101 mBlockShift = 32 - bitCount;
102 mBlockIdMask = ((0x01 << bitCount) - 1) << mBlockShift;
103 mIndexMask = ~mBlockIdMask;
112 // Clean up memory block linked list (mMemoryBlocks will be auto-destroyed by its destructor)
113 Block* block = mMemoryBlocks.nextBlock;
116 Block* nextBlock = block->nextBlock;
123 * @brief Allocate a new block for allocating memory from
125 void AllocateNewBlock()
127 // Double capacity for the new block
128 SizeType size = mCurrentBlockCapacity * 2;
129 if(size > mMaximumBlockCapacity || size < mCurrentBlockCapacity) // Check for overflow of size type
131 size = mMaximumBlockCapacity;
134 mCurrentBlockCapacity = size;
137 Block* block = new Block(mCurrentBlockCapacity * mFixedSize);
138 mCurrentBlock->nextBlock = block; // Add to end of linked list
139 mCurrentBlock = block;
141 mCurrentBlockSize = 0;
143 // Add to main list of blocks
146 mBlocks.push_back(block);
152 * @brief check the memory being free'd exists inside the memory pool
153 * @param[in] memory address of object to remove
155 void CheckMemoryIsInsidePool(const void* const memory)
157 bool inRange = false;
158 const Block* block = &mMemoryBlocks;
162 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
164 if((memory >= block->blockMemory) && (memory < (endOfBlock)))
169 block = block->nextBlock;
171 DALI_ASSERT_DEBUG(inRange && "Freeing memory that does not exist in memory pool");
175 Mutex mMutex; ///< Mutex for thread-safe allocation and deallocation
177 SizeType mFixedSize; ///< The size of each allocation in bytes
179 Block mMemoryBlocks; ///< Linked list of allocated memory blocks
180 SizeType mMaximumBlockCapacity; ///< The maximum allowed capacity of allocations in a new memory block
182 std::vector<Block*> mBlocks; ///< Address of each allocated block
184 Block* mCurrentBlock; ///< Pointer to the active block
185 SizeType mCurrentBlockCapacity; ///< The maximum number of allocations that can be allocated for the current block
186 SizeType mCurrentBlockSize; ///< The number of allocations allocated to the current block
188 SizeType mMaximumBlockCount{0xffffffff}; ///< Maximum number of blocks (or unlimited)
189 SizeType mBlockShift{0x0}; ///< number of bits to shift block id in key
190 SizeType mBlockIdMask{0x0}; ///< mask for key conversion
191 SizeType mIndexMask{0xffffffff}; ///< mask for key conversion
193 void* mDeletedObjects; ///< Pointer to the head of the list of deleted objects. The addresses are stored in the allocated memory blocks.
196 FixedSizeMemoryPool::FixedSizeMemoryPool(
198 SizeType initialCapacity,
199 SizeType maximumBlockCapacity,
200 SizeType maximumBlockCount)
202 mImpl = new Impl(fixedSize, initialCapacity, maximumBlockCapacity, maximumBlockCount);
205 FixedSizeMemoryPool::~FixedSizeMemoryPool()
210 void* FixedSizeMemoryPool::Allocate()
212 // First, recycle deleted objects
213 if(mImpl->mDeletedObjects)
215 void* recycled = mImpl->mDeletedObjects;
216 mImpl->mDeletedObjects = *(reinterpret_cast<void**>(mImpl->mDeletedObjects)); // Pop head off front of deleted objects list
220 // Check if current block is full
221 if(mImpl->mCurrentBlockSize >= mImpl->mCurrentBlockCapacity)
223 mImpl->AllocateNewBlock();
226 // Placement new the object in block memory
227 uint8_t* objectAddress = static_cast<uint8_t*>(mImpl->mCurrentBlock->blockMemory);
228 objectAddress += mImpl->mCurrentBlockSize * mImpl->mFixedSize;
229 mImpl->mCurrentBlockSize++;
231 return objectAddress;
234 void FixedSizeMemoryPool::Free(void* memory)
239 mImpl->CheckMemoryIsInsidePool(memory);
242 // Add memory to head of deleted objects list. Store next address in the same memory space as the old object.
243 *(reinterpret_cast<void**>(memory)) = mImpl->mDeletedObjects;
244 mImpl->mDeletedObjects = memory;
248 void* FixedSizeMemoryPool::AllocateThreadSafe()
250 Mutex::ScopedLock lock(mImpl->mMutex);
254 void FixedSizeMemoryPool::FreeThreadSafe(void* memory)
258 Mutex::ScopedLock lock(mImpl->mMutex);
263 void* FixedSizeMemoryPool::GetPtrFromKey(FixedSizeMemoryPool::KeyType key)
265 #if defined(__LP64__)
266 uint32_t blockId{0u};
267 uint32_t index = key & mImpl->mIndexMask;
269 if(DALI_LIKELY(mImpl->mBlockShift)) // Key contains block id & index within block
271 blockId = (key & mImpl->mBlockIdMask) >> mImpl->mBlockShift;
272 if(blockId < mImpl->mBlocks.size())
274 return static_cast<uint8_t*>(mImpl->mBlocks[blockId]->blockMemory) + mImpl->mFixedSize * index;
280 // Treat as having no block id, and search for Nth item
281 Impl::Block* block = &mImpl->mMemoryBlocks;
282 while(block != nullptr)
284 const SizeType numberOfItems = (block->mBlockSize) / mImpl->mFixedSize;
285 if(index < numberOfItems)
287 return static_cast<uint8_t*>(block->blockMemory) + mImpl->mFixedSize * index;
289 index -= numberOfItems;
290 block = block->nextBlock;
295 // Treat ptrs as keys
296 return static_cast<void*>(key);
300 FixedSizeMemoryPool::KeyType FixedSizeMemoryPool::GetKeyFromPtr(void* ptr)
302 #if defined(__LP64__)
303 uint32_t blockId = 0;
307 // If block count is limited, the bit shift is non-zero.
308 if(DALI_LIKELY(mImpl->mBlockShift))
310 for(auto block : mImpl->mBlocks)
312 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
314 if(block->blockMemory <= ptr && ptr < endOfBlock)
316 index = (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
325 // Block count is unlimited, key is item count. But, potentially have to iterate through many blocks.
326 Impl::Block* block = &mImpl->mMemoryBlocks;
327 while(block != nullptr && !found)
329 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
331 if(block->blockMemory <= ptr && ptr < endOfBlock)
333 index += (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
337 index += block->mBlockSize / mImpl->mFixedSize;
338 block = block->nextBlock;
343 return blockId << mImpl->mBlockShift | (index & mImpl->mIndexMask);
348 return static_cast<FixedSizeMemoryPool::KeyType>(ptr);
352 uint32_t FixedSizeMemoryPool::GetCapacity() const
354 // Ignores deleted objects list, just returns currently allocated size
355 uint32_t totalAllocation = 0;
357 Mutex::ScopedLock lock(mImpl->mMutex);
358 Impl::Block* block = &mImpl->mMemoryBlocks;
361 totalAllocation += block->mBlockSize;
362 block = block->nextBlock;
365 return totalAllocation;
368 } // namespace Internal