From 77940a43f7ef97d6e434ef4649c43384e7387767 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 13 Jan 2020 17:02:59 +0100 Subject: [PATCH] Add new memory allocator HeapAllocator --- CMakeLists.txt | 2 + src/engine/DynamicsWorld.cpp | 3 + src/memory/HeapAllocator.cpp | 272 ++++++++++++++++++++++++++++ src/memory/HeapAllocator.h | 149 +++++++++++++++ src/memory/MemoryManager.cpp | 5 +- src/memory/MemoryManager.h | 10 +- src/systems/ContactSolverSystem.cpp | 7 + src/systems/ContactSolverSystem.h | 3 + 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 src/memory/HeapAllocator.cpp create mode 100644 src/memory/HeapAllocator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ec405f5..2c098296 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ SET (REACTPHYSICS3D_HEADERS "src/memory/MemoryAllocator.h" "src/memory/PoolAllocator.h" "src/memory/SingleFrameAllocator.h" + "src/memory/HeapAllocator.h" "src/memory/DefaultAllocator.h" "src/memory/MemoryManager.h" "src/containers/Stack.h" @@ -267,6 +268,7 @@ SET (REACTPHYSICS3D_SOURCES "src/mathematics/Vector3.cpp" "src/memory/PoolAllocator.cpp" "src/memory/SingleFrameAllocator.cpp" + "src/memory/HeapAllocator.cpp" "src/memory/MemoryManager.cpp" "src/utils/Profiler.cpp" "src/utils/Logger.cpp" diff --git a/src/engine/DynamicsWorld.cpp b/src/engine/DynamicsWorld.cpp index 8b050e6e..24e55327 100644 --- a/src/engine/DynamicsWorld.cpp +++ b/src/engine/DynamicsWorld.cpp @@ -180,6 +180,9 @@ void DynamicsWorld::solveContactsAndConstraints(decimal timeStep) { } mContactSolverSystem.storeImpulses(); + + // Reset the contact solver + mContactSolverSystem.reset(); } // Solve the position error correction of the constraints diff --git a/src/memory/HeapAllocator.cpp b/src/memory/HeapAllocator.cpp new file mode 100644 index 00000000..2988ab90 --- /dev/null +++ b/src/memory/HeapAllocator.cpp @@ -0,0 +1,272 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 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 "HeapAllocator.h" +#include "MemoryManager.h" +#include +#include +#include + +using namespace reactphysics3d; + +size_t HeapAllocator::INIT_ALLOCATED_SIZE = 1024; + +// Constructor +HeapAllocator::HeapAllocator(MemoryAllocator& baseAllocator) + : mBaseAllocator(baseAllocator), mAllocatedMemory(0), mMemoryUnits(nullptr), mCachedFreeUnit(nullptr), + mNbTimesAllocateMethodCalled(0), mDebug(baseAllocator) { + + reserve(INIT_ALLOCATED_SIZE); +} + +// Destructor +HeapAllocator::~HeapAllocator() { + + for (auto it = mDebug.begin(); it != mDebug.end(); ++it) { + std::cout << "Size: " << (*it).first << " -> " << (*it).second << std::endl; + } + +#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 + + // Release the memory allocated for memory unit + MemoryUnitHeader* unit = mMemoryUnits; + while (unit != nullptr) { + + assert(!unit->isAllocated); + + MemoryUnitHeader* nextUnit = unit->nextUnit; + + // Destroy the unit + unit->~MemoryUnitHeader(); + mBaseAllocator.release(static_cast(unit), unit->size + sizeof(MemoryUnitHeader)); + + unit = nextUnit; + } +} + +/// Split a memory unit in two units. One of size "size" and the second with +/// left over space. The second unit is put into the free memory units +void HeapAllocator::splitMemoryUnit(MemoryUnitHeader* unit, size_t size) { + + assert(size <= unit->size); + assert(!unit->isAllocated); + + // Split the free memory unit in two memory units, one with the requested memory size + // and a second one with the left over space + if (size + sizeof(MemoryUnitHeader) < unit->size) { + + assert(unit->size - size > 0); + + // Create a new memory unit with left over space + unsigned char* newUnitLocation = (reinterpret_cast(unit)) + sizeof(MemoryUnitHeader) + size; + MemoryUnitHeader* newUnit = new (static_cast(newUnitLocation)) MemoryUnitHeader(unit->size - sizeof(MemoryUnitHeader) - size, unit, unit->nextUnit, unit->isNextContiguousMemory); + assert(newUnit->nextUnit != newUnit); + unit->nextUnit = newUnit; + if (newUnit->nextUnit != nullptr) { + newUnit->nextUnit->previousUnit = newUnit; + } + assert(unit->nextUnit != unit); + unit->isNextContiguousMemory = true; + unit->size = size; + + assert(unit->previousUnit == nullptr || unit->previousUnit->nextUnit == unit); + assert(unit->nextUnit == nullptr || unit->nextUnit->previousUnit == unit); + + assert(newUnit->previousUnit == nullptr || newUnit->previousUnit->nextUnit == newUnit); + assert(newUnit->nextUnit == nullptr || newUnit->nextUnit->previousUnit == newUnit); + } +} + +// Allocate memory of a given size (in bytes) and return a pointer to the +// allocated memory. +void* HeapAllocator::allocate(size_t size) { + + assert(size > 0); + + // We cannot allocate zero bytes + if (size == 0) return nullptr; + + if (mDebug.containsKey(size)) { + mDebug[size]++; + } + else { + mDebug.add(Pair(size, 1)); + } + +#ifndef NDEBUG + mNbTimesAllocateMethodCalled++; +#endif + + MemoryUnitHeader* currentUnit = mMemoryUnits; + assert(mMemoryUnits->previousUnit == nullptr); + + // If there is a cached free memory unit + if (mCachedFreeUnit != nullptr) { + assert(!mCachedFreeUnit->isAllocated); + + // If the cached free memory unit matches the request + if (size <= mCachedFreeUnit->size) { + currentUnit = mCachedFreeUnit; + mCachedFreeUnit = nullptr; + } + } + + // For each memory unit + while (currentUnit != nullptr) { + + // If we have found a free memory unit with size large enough for the allocation request + if (!currentUnit->isAllocated && size <= currentUnit->size) { + + // Split the free memory unit in two memory units, one with the requested memory size + // and a second one with the left over space + splitMemoryUnit(currentUnit, size); + + break; + } + + currentUnit = currentUnit->nextUnit; + } + + // If we have not found a large enough memory unit we need to allocate more memory + if (currentUnit == nullptr) { + + reserve((mAllocatedMemory + size) * 2); + + assert(mCachedFreeUnit != nullptr); + assert(!mCachedFreeUnit->isAllocated); + + // The cached free memory unit is large enough at this point + currentUnit = mCachedFreeUnit; + + assert(currentUnit->size >= size); + + splitMemoryUnit(currentUnit, size); + } + + currentUnit->isAllocated = true; + + // Cache the next memory unit if it is not allocated + if (currentUnit->nextUnit != nullptr && !currentUnit->nextUnit->isAllocated) { + mCachedFreeUnit = currentUnit->nextUnit; + } + + // Return a pointer to the memory area inside the unit + return static_cast(reinterpret_cast(currentUnit) + sizeof(MemoryUnitHeader)); +} + +// Release previously allocated memory. +void HeapAllocator::release(void* pointer, size_t size) { + + assert(size > 0); + + // Cannot release a 0-byte allocated memory + if (size == 0) return; + + mDebug[size]--; + if (mDebug[size] == 0) { + mDebug.remove(size); + } + +#ifndef NDEBUG + mNbTimesAllocateMethodCalled--; +#endif + + unsigned char* unitLocation = static_cast(pointer) - sizeof(MemoryUnitHeader); + MemoryUnitHeader* unit = reinterpret_cast(unitLocation); + assert(unit->isAllocated); + unit->isAllocated = false; + + MemoryUnitHeader* currentUnit = unit; + + // If the previous unit is not allocated and memory is contiguous to the current unit + if (unit->previousUnit != nullptr && !unit->previousUnit->isAllocated && unit->previousUnit->isNextContiguousMemory) { + + currentUnit = unit->previousUnit; + + // Merge the two contiguous memory units + mergeUnits(unit->previousUnit, unit); + } + + // If the next unit is not allocated and memory is contiguous to the current unit + if (currentUnit->nextUnit != nullptr && !currentUnit->nextUnit->isAllocated && currentUnit->isNextContiguousMemory) { + + // Merge the two contiguous memory units + mergeUnits(currentUnit, currentUnit->nextUnit); + } + + mCachedFreeUnit = currentUnit; +} + +// Merge two contiguous memory units that are not allocated. +/// Memory unit 2 will be merged into memory unit 1 and memory unit 2 will be removed +void HeapAllocator::mergeUnits(MemoryUnitHeader* unit1, MemoryUnitHeader* unit2) { + + assert(unit2->previousUnit == unit1); + assert(unit1->nextUnit == unit2); + assert(!unit1->isAllocated); + assert(!unit2->isAllocated); + assert(unit1->isNextContiguousMemory); + + unit1->size += unit2->size + sizeof(MemoryUnitHeader); + unit1->nextUnit = unit2->nextUnit; + assert(unit1->nextUnit != unit1); + if (unit2->nextUnit != nullptr) { + unit2->nextUnit->previousUnit = unit1; + } + unit1->isNextContiguousMemory = unit2->isNextContiguousMemory; + + // Destroy unit 2 + unit2->~MemoryUnitHeader(); + + assert(unit1->previousUnit == nullptr || unit1->previousUnit->nextUnit == unit1); + assert(unit1->nextUnit == nullptr || unit1->nextUnit->previousUnit == unit1); +} + +// Reserve more memory for the allocator +void HeapAllocator::reserve(size_t sizeToAllocate) { + + // Allocate memory + void* memory = mBaseAllocator.allocate(sizeToAllocate + sizeof(MemoryUnitHeader)); + assert(memory != nullptr); + + // Create a new memory unit for the allocated memory + MemoryUnitHeader* memoryUnit = new (memory) MemoryUnitHeader(sizeToAllocate, nullptr, mMemoryUnits, false); + + if (mMemoryUnits != nullptr) { + mMemoryUnits->previousUnit = memoryUnit; + } + + // Add the memory unit at the beginning of the linked-list of memory units + mMemoryUnits = memoryUnit; + + mCachedFreeUnit = mMemoryUnits; + + mAllocatedMemory += sizeToAllocate; +} diff --git a/src/memory/HeapAllocator.h b/src/memory/HeapAllocator.h new file mode 100644 index 00000000..28f4e268 --- /dev/null +++ b/src/memory/HeapAllocator.h @@ -0,0 +1,149 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 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. * +* * +********************************************************************************/ + +#ifndef REACTPHYSICS3D_HEAP_ALLOCATOR_H +#define REACTPHYSICS3D_HEAP_ALLOCATOR_H + +// Libraries +#include "configuration.h" +#include "MemoryAllocator.h" +#include +#include + +/// ReactPhysics3D namespace +namespace reactphysics3d { + +// Class HeapAllocator +/** + * This class is used to efficiently allocate memory on the heap. + * It is used to allocate memory that cannot be allocated in a single frame allocator or a pool allocator. + */ +class HeapAllocator : public MemoryAllocator { + + private : + + // -------------------- Internal Classes -------------------- // + + // Structure MemoryUnitHeader + /** + * Represent the header of a memory unit in the heap + */ + struct MemoryUnitHeader { + + public : + + // -------------------- Attributes -------------------- // + + /// Size in bytes of the allocated memory unit + size_t size; + + /// True if the memory unit is currently allocated + bool isAllocated; + + /// Pointer to the previous memory unit + MemoryUnitHeader* previousUnit; + + /// Pointer to the next memory unit + MemoryUnitHeader* nextUnit; + + /// True if the next memory unit has been allocated with the same call to malloc() + bool isNextContiguousMemory; + + // -------------------- Methods -------------------- // + + MemoryUnitHeader(size_t size, MemoryUnitHeader* previousUnit, MemoryUnitHeader* nextUnit, bool isNextContiguousMemory) + : size(size), isAllocated(false), previousUnit(previousUnit), + nextUnit(nextUnit), isNextContiguousMemory(isNextContiguousMemory) { + + assert(size > 0); + } + + }; + + // -------------------- Constants -------------------- // + + static size_t INIT_ALLOCATED_SIZE; + + // -------------------- Attributes -------------------- // + + /// Base memory allocator + MemoryAllocator& mBaseAllocator; + + /// Allocated memory (in bytes) + size_t mAllocatedMemory; + + /// Pointer to the first memory unit of the linked-list + MemoryUnitHeader* mMemoryUnits; + + /// Pointer to a cached free memory unit + MemoryUnitHeader* mCachedFreeUnit; + + // TODO : REMOVE THIS + Map mDebug; + +#ifndef NDEBUG + /// This variable is incremented by one when the allocate() method has been + /// called and decreased by one when the release() method has been called. + /// This variable is used in debug mode to check that the allocate() and release() + /// methods are called the same number of times + int mNbTimesAllocateMethodCalled; +#endif + + // -------------------- Methods -------------------- // + + /// Split a memory unit in two units. One of size "size" and the second with + /// left over space. The second unit is put into the free memory units + void splitMemoryUnit(MemoryUnitHeader* unit, size_t size); + + // Merge two contiguous memory units that are not allocated. + void mergeUnits(MemoryUnitHeader* unit1, MemoryUnitHeader* unit2); + + public : + + // -------------------- Methods -------------------- // + + /// Constructor + HeapAllocator(MemoryAllocator& baseAllocator); + + /// Destructor + virtual ~HeapAllocator() override; + + /// Assignment operator + HeapAllocator& operator=(HeapAllocator& allocator) = default; + + /// Allocate memory of a given size (in bytes) and return a pointer to the + /// allocated memory. + virtual void* allocate(size_t size) override; + + /// Release previously allocated memory. + virtual void release(void* pointer, size_t size) override; + + /// Reserve more memory for the allocator + void reserve(size_t sizeToAllocate); +}; + +} + +#endif diff --git a/src/memory/MemoryManager.cpp b/src/memory/MemoryManager.cpp index bc197ac6..3590d6d0 100644 --- a/src/memory/MemoryManager.cpp +++ b/src/memory/MemoryManager.cpp @@ -31,7 +31,8 @@ using namespace reactphysics3d; // Constructor MemoryManager::MemoryManager(MemoryAllocator* baseAllocator) : mBaseAllocator(baseAllocator == nullptr ? &mDefaultAllocator : baseAllocator), - mPoolAllocator(baseAllocator == nullptr ? mDefaultAllocator : *baseAllocator), - mSingleFrameAllocator(baseAllocator == nullptr ? mDefaultAllocator : *baseAllocator) { + mHeapAllocator(*mBaseAllocator), + mPoolAllocator(mHeapAllocator), + mSingleFrameAllocator(mHeapAllocator) { } diff --git a/src/memory/MemoryManager.h b/src/memory/MemoryManager.h index 9335c074..1dd05076 100644 --- a/src/memory/MemoryManager.h +++ b/src/memory/MemoryManager.h @@ -29,6 +29,7 @@ // Libraries #include "memory/DefaultAllocator.h" #include "memory/PoolAllocator.h" +#include "memory/HeapAllocator.h" #include "memory/SingleFrameAllocator.h" /// Namespace ReactPhysics3D @@ -40,7 +41,11 @@ class MemoryAllocator; // Class MemoryManager /** * The memory manager is used to store the different memory allocators that are used - * by the library. + * by the library. The base allocator is either the default allocator (malloc/free) of a custom + * allocated specified by the user. The HeapAllocator is used on top of the base allocator. + * The SingleFrameAllocator is used for memory that is allocated only during a frame and the PoolAllocator + * is used to allocated objects of small size. Both SingleFrameAllocator and PoolAllocator will fall back to + * HeapAllocator if an allocation request cannot be fulfilled. */ class MemoryManager { @@ -52,6 +57,9 @@ class MemoryManager { /// Pointer to the base memory allocator to use MemoryAllocator* mBaseAllocator; + /// Memory heap allocator + HeapAllocator mHeapAllocator; + /// Memory pool allocator PoolAllocator mPoolAllocator; diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 06cb7c30..a07c5070 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -99,6 +99,13 @@ void ContactSolverSystem::init(List* contactManifolds, Listsize() > 0) mMemoryManager.release(MemoryManager::AllocationType::Frame, mContactPoints, sizeof(ContactPointSolver) * mAllContactPoints->size()); + if (mAllContactManifolds->size() > 0) mMemoryManager.release(MemoryManager::AllocationType::Frame, mContactConstraints, sizeof(ContactManifoldSolver) * mAllContactManifolds->size()); +} + // Initialize the constraint solver for a given island void ContactSolverSystem::initializeForIsland(uint islandIndex) { diff --git a/src/systems/ContactSolverSystem.h b/src/systems/ContactSolverSystem.h index 7aeceb64..f795e6e4 100644 --- a/src/systems/ContactSolverSystem.h +++ b/src/systems/ContactSolverSystem.h @@ -382,6 +382,9 @@ class ContactSolverSystem { /// Solve the contacts void solve(); + /// Release allocated memory + void reset(); + /// Return true if the split impulses position correction technique is used for contacts bool isSplitImpulseActive() const;