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
46 KeyType mIndexOffset; ///< The Offset of this block's index
48 SizeType mBlockSize; ///< Size of the block in bytes
51 * @brief Construct a new block with given size
53 * @param size The size of the memory block to allocate in bytes. Must be non-zero.
62 blockMemory = ::operator new(size);
63 DALI_ASSERT_ALWAYS(blockMemory && "Out of memory");
71 ::operator delete(blockMemory);
76 Block(const Block& block);
79 Block& operator=(const Block& block);
85 Impl(SizeType fixedSize, SizeType initialCapacity, SizeType maximumBlockCapacity, SizeType maximumBlockCount)
87 mFixedSize(fixedSize),
88 mMemoryBlocks(initialCapacity * mFixedSize),
89 mMaximumBlockCapacity(maximumBlockCapacity),
90 mCurrentBlock(&mMemoryBlocks),
91 mCurrentBlockCapacity(initialCapacity),
93 mMaximumBlockCount(maximumBlockCount),
94 mDeletedObjects(nullptr)
96 // We need enough room to store the deleted list in the data
97 DALI_ASSERT_DEBUG(mFixedSize >= sizeof(void*));
100 if(mMaximumBlockCount < 0xffffffff)
102 // Only use mBlocks for key/ptr conversion with max number of blocks
104 mBlocks.push_back(&mMemoryBlocks);
106 // Compute masks and shifts
107 int bitCount = (logf(mMaximumBlockCount) / logf(2.0f)) + 1;
108 mBlockShift = 32 - bitCount;
109 mBlockIdMask = ((0x01 << bitCount) - 1) << mBlockShift;
110 mIndexMask = ~mBlockIdMask;
120 // Clean up memory block linked list (mMemoryBlocks will be auto-destroyed by its destructor)
121 Block* block = mMemoryBlocks.nextBlock;
124 Block* nextBlock = block->nextBlock;
131 * @brief Allocate a new block for allocating memory from
133 void AllocateNewBlock()
135 // Double capacity for the new block
136 SizeType size = mCurrentBlockCapacity * 2;
137 if(size > mMaximumBlockCapacity || size < mCurrentBlockCapacity) // Check for overflow of size type
139 size = mMaximumBlockCapacity;
142 mCurrentBlockCapacity = size;
145 Block* block = new Block(mCurrentBlockCapacity * mFixedSize);
147 #if defined(__LP64__)
148 if(mBlockShift) // Key contains block id & index within block
150 // Add to main list of blocks
151 mBlocks.push_back(block);
153 mCurrentBlock->nextBlock = block; // Add end of linked list
154 mCurrentBlock = block;
155 #if defined(__LP64__)
159 // Heuristic optimization. Make bigger block positioned at front, instead of header.
160 // (Since change the Block value might be heavy)
161 if(mCurrentBlock == &mMemoryBlocks)
163 block->mIndexOffset = mMemoryBlocks.mBlockSize / mFixedSize;
165 // Add to end of linked list.
166 // Now mCurrentBlock is next block of header.
167 mCurrentBlock->nextBlock = block;
168 mCurrentBlock = block;
172 block->mIndexOffset = mCurrentBlock->mIndexOffset + (mCurrentBlock->mBlockSize / mFixedSize);
174 // Add new block as next of header.
175 mMemoryBlocks.nextBlock = block;
177 // Make previous mCurrentBlock as next of new block.
178 block->nextBlock = mCurrentBlock;
180 // Keep mCurrentBlock is next block of mMemoryBlocks.
181 mCurrentBlock = block;
186 mCurrentBlockSize = 0;
191 * @brief check the memory being free'd exists inside the memory pool
192 * @param[in] memory address of object to remove
194 void CheckMemoryIsInsidePool(const void* const memory)
196 bool inRange = false;
197 const Block* block = &mMemoryBlocks;
201 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
203 if((memory >= block->blockMemory) && (memory < (endOfBlock)))
208 block = block->nextBlock;
210 DALI_ASSERT_DEBUG(inRange && "Freeing memory that does not exist in memory pool");
214 Mutex mMutex; ///< Mutex for thread-safe allocation and deallocation
216 SizeType mFixedSize; ///< The size of each allocation in bytes
218 Block mMemoryBlocks; ///< Linked list of allocated memory blocks
219 SizeType mMaximumBlockCapacity; ///< The maximum allowed capacity of allocations in a new memory block
221 #if defined(__LP64__)
222 std::vector<Block*> mBlocks; ///< Address of each allocated block
225 Block* mCurrentBlock; ///< Pointer to the active block
226 SizeType mCurrentBlockCapacity; ///< The maximum number of allocations that can be allocated for the current block
227 SizeType mCurrentBlockSize; ///< The number of allocations allocated to the current block
229 SizeType mMaximumBlockCount{0xffffffff}; ///< Maximum number of blocks (or unlimited)
230 #if defined(__LP64__)
231 SizeType mBlockShift{0x0}; ///< number of bits to shift block id in key
232 SizeType mBlockIdMask{0x0}; ///< mask for key conversion
233 SizeType mIndexMask{0xffffffff}; ///< mask for key conversion
236 void* mDeletedObjects; ///< Pointer to the head of the list of deleted objects. The addresses are stored in the allocated memory blocks.
239 FixedSizeMemoryPool::FixedSizeMemoryPool(
241 SizeType initialCapacity,
242 SizeType maximumBlockCapacity,
243 SizeType maximumBlockCount)
245 mImpl = new Impl(fixedSize, initialCapacity, maximumBlockCapacity, maximumBlockCount);
248 FixedSizeMemoryPool::~FixedSizeMemoryPool()
253 void* FixedSizeMemoryPool::Allocate()
255 // First, recycle deleted objects
256 if(mImpl->mDeletedObjects)
258 void* recycled = mImpl->mDeletedObjects;
259 mImpl->mDeletedObjects = *(reinterpret_cast<void**>(mImpl->mDeletedObjects)); // Pop head off front of deleted objects list
263 // Check if current block is full
264 if(mImpl->mCurrentBlockSize >= mImpl->mCurrentBlockCapacity)
266 mImpl->AllocateNewBlock();
269 // Placement new the object in block memory
270 uint8_t* objectAddress = static_cast<uint8_t*>(mImpl->mCurrentBlock->blockMemory);
271 objectAddress += mImpl->mCurrentBlockSize * mImpl->mFixedSize;
272 mImpl->mCurrentBlockSize++;
274 return objectAddress;
277 void FixedSizeMemoryPool::Free(void* memory)
282 mImpl->CheckMemoryIsInsidePool(memory);
285 // Add memory to head of deleted objects list. Store next address in the same memory space as the old object.
286 *(reinterpret_cast<void**>(memory)) = mImpl->mDeletedObjects;
287 mImpl->mDeletedObjects = memory;
291 void* FixedSizeMemoryPool::AllocateThreadSafe()
293 Mutex::ScopedLock lock(mImpl->mMutex);
297 void FixedSizeMemoryPool::FreeThreadSafe(void* memory)
301 Mutex::ScopedLock lock(mImpl->mMutex);
306 void* FixedSizeMemoryPool::GetPtrFromKey(FixedSizeMemoryPool::KeyType key)
308 #if defined(__LP64__)
309 uint32_t blockId{0u};
310 uint32_t index = key & mImpl->mIndexMask;
312 if(DALI_LIKELY(mImpl->mBlockShift)) // Key contains block id & index within block
314 blockId = (key & mImpl->mBlockIdMask) >> mImpl->mBlockShift;
315 if(blockId < mImpl->mBlocks.size())
317 return static_cast<uint8_t*>(mImpl->mBlocks[blockId]->blockMemory) + mImpl->mFixedSize * index;
323 // Treat as having no block id, and search for Nth item
324 Impl::Block* block = &mImpl->mMemoryBlocks;
325 while(block != nullptr)
327 const uint32_t indexOffset = block->mIndexOffset;
328 const SizeType numberOfItems = (block->mBlockSize) / mImpl->mFixedSize;
329 if(indexOffset <= index && index < indexOffset + numberOfItems)
331 return static_cast<uint8_t*>(block->blockMemory) + mImpl->mFixedSize * (index - indexOffset);
333 block = block->nextBlock;
338 // Treat ptrs as keys
339 return static_cast<void*>(key);
343 FixedSizeMemoryPool::KeyType FixedSizeMemoryPool::GetKeyFromPtr(void* ptr)
345 #if defined(__LP64__)
346 uint32_t blockId = 0;
350 // If block count is limited, the bit shift is non-zero.
351 if(DALI_LIKELY(mImpl->mBlockShift))
353 // Iterate backward so we can search at bigger blocks first.
354 blockId = mImpl->mBlocks.size();
355 for(auto iter = mImpl->mBlocks.rbegin(), iterEnd = mImpl->mBlocks.rend(); iter != iterEnd; ++iter)
360 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
362 if(block->blockMemory <= ptr && ptr < endOfBlock)
364 index = (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
372 // Block count is unlimited, key is item count. But, potentially have to iterate through many blocks.
373 Impl::Block* block = &mImpl->mMemoryBlocks;
374 while(block != nullptr && !found)
376 const void* const endOfBlock = reinterpret_cast<char*>(block->blockMemory) + block->mBlockSize;
378 if(block->blockMemory <= ptr && ptr < endOfBlock)
380 index = block->mIndexOffset + (static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(block->blockMemory)) / mImpl->mFixedSize;
384 block = block->nextBlock;
389 return blockId << mImpl->mBlockShift | (index & mImpl->mIndexMask);
394 return static_cast<FixedSizeMemoryPool::KeyType>(ptr);
398 uint32_t FixedSizeMemoryPool::GetCapacity() const
400 // Ignores deleted objects list, just returns currently allocated size
401 uint32_t totalAllocation = 0;
403 Mutex::ScopedLock lock(mImpl->mMutex);
404 Impl::Block* block = &mImpl->mMemoryBlocks;
407 totalAllocation += block->mBlockSize;
408 block = block->nextBlock;
411 return totalAllocation;
414 } // namespace Internal