reactphysics3d/src/memory/PoolAllocator.cpp
2023-02-08 12:49:29 +13:00

227 lines
9.3 KiB
C++

/********************************************************************************
* ReactPhysics3D physics library, http://www.reactphysics3d.com *
* Copyright (c) 2010-2022 Daniel Chappuis *
*********************************************************************************
* *
* This software is provided 'as-is', without any express or implied warranty. *
* In no event will the authors be held liable for any damages arising from the *
* use of this software. *
* *
* Permission is granted to anyone to use this software for any purpose, *
* including commercial applications, and to alter it and redistribute it *
* freely, subject to the following restrictions: *
* *
* 1. The origin of this software must not be misrepresented; you must not claim *
* that you wrote the original software. If you use this software in a *
* product, an acknowledgment in the product documentation would be *
* appreciated but is not required. *
* *
* 2. Altered source versions must be plainly marked as such, and must not be *
* misrepresented as being the original software. *
* *
* 3. This notice may not be removed or altered from any source distribution. *
* *
********************************************************************************/
// Libraries
#include <reactphysics3d/memory/PoolAllocator.h>
#include <reactphysics3d/memory/MemoryManager.h>
#include <cstdlib>
#include <cassert>
using namespace reactphysics3d;
// Initialization of static variables
bool PoolAllocator::isMapSizeToHeadIndexInitialized = false;
size_t PoolAllocator::mUnitSizes[NB_HEAPS];
int PoolAllocator::mMapSizeToHeapIndex[MAX_UNIT_SIZE + 1];
// Constructor
PoolAllocator::PoolAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(baseAllocator) {
// Allocate some memory to manage the blocks
mNbAllocatedMemoryBlocks = 64;
mNbCurrentMemoryBlocks = 0;
const size_t sizeToAllocate = mNbAllocatedMemoryBlocks * sizeof(MemoryBlock);
mMemoryBlocks = static_cast<MemoryBlock*>(baseAllocator.allocate(sizeToAllocate));
memset(mMemoryBlocks, 0, sizeToAllocate);
memset(mFreeMemoryUnits, 0, sizeof(mFreeMemoryUnits));
#ifndef NDEBUG
mNbTimesAllocateMethodCalled = 0;
#endif
// If the mMapSizeToHeapIndex has not been initialized yet
if (!isMapSizeToHeadIndexInitialized) {
// Initialize the array that contains the sizes of the memory units that will
// be allocated in each different heap
for (uint i=0; i < NB_HEAPS; i++) {
mUnitSizes[i] = (i+1) * MIN_UNIT_SIZE;
}
// Initialize the lookup table that maps the size to allocated to the
// corresponding heap we will use for the allocation
uint j = 0;
mMapSizeToHeapIndex[0] = -1; // This element should not be used
for (uint i=1; i <= MAX_UNIT_SIZE; i++) {
if (i <= mUnitSizes[j]) {
mMapSizeToHeapIndex[i] = j;
}
else {
j++;
mMapSizeToHeapIndex[i] = j;
}
}
isMapSizeToHeadIndexInitialized = true;
}
}
// Destructor
PoolAllocator::~PoolAllocator() {
// Release the memory allocated for each block
for (uint i=0; i<mNbCurrentMemoryBlocks; i++) {
mBaseAllocator.release(mMemoryBlocks[i].memoryUnits, BLOCK_SIZE);
}
mBaseAllocator.release(mMemoryBlocks, mNbAllocatedMemoryBlocks * sizeof(MemoryBlock));
#ifndef NDEBUG
// Check that the allocate() and release() methods have been called the same
// number of times to avoid memory leaks.
assert(mNbTimesAllocateMethodCalled == 0);
#endif
}
// Allocate memory of a given size (in bytes) and return a pointer to the
// allocated memory.
void* PoolAllocator::allocate(size_t size) {
// Lock the method with a mutex
std::lock_guard<std::mutex> lock(mMutex);
assert(size > 0);
// We cannot allocate zero bytes
if (size == 0) return nullptr;
#ifndef NDEBUG
mNbTimesAllocateMethodCalled++;
#endif
// If we need to allocate more than the maximum memory unit size
if (size > MAX_UNIT_SIZE) {
// Allocate memory using default allocation
void* allocatedMemory = mBaseAllocator.allocate(size);
// Check that allocated memory is 8-bytes aligned
assert(reinterpret_cast<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
return allocatedMemory;
}
// Get the index of the heap that will take care of the allocation request
int indexHeap = mMapSizeToHeapIndex[size];
assert(indexHeap >= 0 && indexHeap < NB_HEAPS);
// If there still are free memory units in the corresponding heap
if (mFreeMemoryUnits[indexHeap] != nullptr) {
// Return a pointer to the memory unit
MemoryUnit* unit = mFreeMemoryUnits[indexHeap];
mFreeMemoryUnits[indexHeap] = unit->nextUnit;
void* allocatedMemory = static_cast<void*>(unit);
// Check that allocated memory is 8-bytes aligned
assert(reinterpret_cast<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
return allocatedMemory;
}
else { // If there is no more free memory units in the corresponding heap
// If we need to allocate more memory to contains the blocks
if (mNbCurrentMemoryBlocks == mNbAllocatedMemoryBlocks) {
// Allocate more memory to contain the blocks
MemoryBlock* currentMemoryBlocks = mMemoryBlocks;
mNbAllocatedMemoryBlocks += 64;
mMemoryBlocks = static_cast<MemoryBlock*>(mBaseAllocator.allocate(mNbAllocatedMemoryBlocks * sizeof(MemoryBlock)));
memcpy(mMemoryBlocks, currentMemoryBlocks, mNbCurrentMemoryBlocks * sizeof(MemoryBlock));
memset(mMemoryBlocks + mNbCurrentMemoryBlocks, 0, 64 * sizeof(MemoryBlock));
mBaseAllocator.release(currentMemoryBlocks, mNbCurrentMemoryBlocks * sizeof(MemoryBlock));
}
// Allocate a new memory blocks for the corresponding heap and divide it in many
// memory units
MemoryBlock* newBlock = mMemoryBlocks + mNbCurrentMemoryBlocks;
newBlock->memoryUnits = static_cast<MemoryUnit*>(mBaseAllocator.allocate(BLOCK_SIZE));
assert(newBlock->memoryUnits != nullptr);
size_t unitSize = mUnitSizes[indexHeap];
size_t nbUnits = BLOCK_SIZE / unitSize;
assert(nbUnits * unitSize <= BLOCK_SIZE);
void* memoryUnitsStart = static_cast<void*>(newBlock->memoryUnits);
char* memoryUnitsStartChar = static_cast<char*>(memoryUnitsStart);
for (size_t i=0; i < nbUnits - 1; i++) {
void* unitPointer = static_cast<void*>(memoryUnitsStartChar + unitSize * i);
void* nextUnitPointer = static_cast<void*>(memoryUnitsStartChar + unitSize * (i+1));
MemoryUnit* unit = static_cast<MemoryUnit*>(unitPointer);
MemoryUnit* nextUnit = static_cast<MemoryUnit*>(nextUnitPointer);
unit->nextUnit = nextUnit;
}
void* lastUnitPointer = static_cast<void*>(memoryUnitsStartChar + unitSize*(nbUnits-1));
MemoryUnit* lastUnit = static_cast<MemoryUnit*>(lastUnitPointer);
lastUnit->nextUnit = nullptr;
// Add the new allocated block into the list of free memory units in the heap
mFreeMemoryUnits[indexHeap] = newBlock->memoryUnits->nextUnit;
mNbCurrentMemoryBlocks++;
void* allocatedMemory = newBlock->memoryUnits;
// Check that allocated memory is 8-bytes aligned
assert(reinterpret_cast<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
// Return the pointer to the first memory unit of the new allocated block
return allocatedMemory;
}
}
// Release previously allocated memory.
void PoolAllocator::release(void* pointer, size_t size) {
// Lock the method with a mutex
std::lock_guard<std::mutex> lock(mMutex);
assert(size > 0);
// Cannot release a 0-byte allocated memory
if (size == 0) return;
#ifndef NDEBUG
mNbTimesAllocateMethodCalled--;
#endif
// If the size is larger than the maximum memory unit size
if (size > MAX_UNIT_SIZE) {
// Release the memory using the default deallocation
mBaseAllocator.release(pointer, size);
return;
}
// Get the index of the heap that has handled the corresponding allocation request
int indexHeap = mMapSizeToHeapIndex[size];
assert(indexHeap >= 0 && indexHeap < NB_HEAPS);
// Insert the released memory unit into the list of free memory units of the
// corresponding heap
MemoryUnit* releasedUnit = static_cast<MemoryUnit*>(pointer);
releasedUnit->nextUnit = mFreeMemoryUnits[indexHeap];
mFreeMemoryUnits[indexHeap] = releasedUnit;
}