diff --git a/CHANGELOG.md b/CHANGELOG.md index 31510eaf..900a83b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### Changed + - The library must now be compiled with C++ 17 compiler + - If the user sets its custom allocator, the return allocated memory must now be 16 bytes aligned + ### Removed ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index c18a8c65..6bb2eaac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # Minimum cmake version required -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.10) # Project configuration project(ReactPhysics3D VERSION 0.9.0 LANGUAGES CXX) @@ -242,7 +242,7 @@ add_library(reactphysics3d ${REACTPHYSICS3D_HEADERS} ${REACTPHYSICS3D_SOURCES}) add_library(ReactPhysics3D::reactphysics3d ALIAS reactphysics3d) # C++11 compiler features -target_compile_features(reactphysics3d PUBLIC cxx_std_11) +target_compile_features(reactphysics3d PUBLIC cxx_std_17) set_target_properties(reactphysics3d PROPERTIES CXX_EXTENSIONS OFF) # Library headers diff --git a/include/reactphysics3d/configuration.h b/include/reactphysics3d/configuration.h index 6f2de958..7e6efdd5 100644 --- a/include/reactphysics3d/configuration.h +++ b/include/reactphysics3d/configuration.h @@ -129,6 +129,9 @@ constexpr uint16 NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD = 256; /// Distance threshold to consider that two contact points in a manifold are the same constexpr decimal SAME_CONTACT_POINT_DISTANCE_THRESHOLD = decimal(0.01); +/// Global alignment (in bytes) that all allocators must enforce +constexpr uint8 GLOBAL_ALIGNMENT = 16; + /// Current version of ReactPhysics3D const std::string RP3D_VERSION = std::string("0.9.0"); diff --git a/include/reactphysics3d/memory/DefaultAllocator.h b/include/reactphysics3d/memory/DefaultAllocator.h index ec2ef0ae..d928741c 100644 --- a/include/reactphysics3d/memory/DefaultAllocator.h +++ b/include/reactphysics3d/memory/DefaultAllocator.h @@ -28,15 +28,19 @@ // Libraries #include +#include #include #include +#include /// ReactPhysics3D namespace namespace reactphysics3d { // Class DefaultAllocator /** - * This class represents a default memory allocator that uses default malloc/free methods + * This class represents a default memory allocator that uses standard C++ functions + * to allocated 16-bytes aligned memory. + * */ class DefaultAllocator : public MemoryAllocator { @@ -49,15 +53,33 @@ class DefaultAllocator : public MemoryAllocator { DefaultAllocator& operator=(DefaultAllocator& allocator) = default; /// Allocate memory of a given size (in bytes) and return a pointer to the - /// allocated memory. + /// allocated memory. The returned allocated memory must be 16 bytes aligned. virtual void* allocate(size_t size) override { - return std::malloc(size); +// If compiler is Visual Studio +#ifdef _MSC_VER + + // Visual Studio doesn't not support standard std:aligned_alloc() method from c++ 17 + return _alligned_malloc(size, GLOBAL_ALIGNMENT); +#else + + // Return 16-bytes aligned memory + return std::aligned_alloc(GLOBAL_ALIGNMENT, size); +#endif } /// Release previously allocated memory. virtual void release(void* pointer, size_t /*size*/) override { - std::free(pointer); + + // If compiler is Visual Studio +#ifdef _MSC_VER + + // Visual Studio doesn't not support standard std:aligned_alloc() method from c++ 17 + return _aligned_free(GLOBAL_ALIGNMENT, pointer); +#else + + return std::free(pointer); +#endif } }; diff --git a/include/reactphysics3d/memory/MemoryAllocator.h b/include/reactphysics3d/memory/MemoryAllocator.h index 9111f3d7..0e732a44 100644 --- a/include/reactphysics3d/memory/MemoryAllocator.h +++ b/include/reactphysics3d/memory/MemoryAllocator.h @@ -50,7 +50,7 @@ class MemoryAllocator { MemoryAllocator& operator=(MemoryAllocator& allocator) = default; /// Allocate memory of a given size (in bytes) and return a pointer to the - /// allocated memory. + /// allocated memory. The return allocated memory must be 16 bytes aligned. virtual void* allocate(size_t size)=0; /// Release previously allocated memory. diff --git a/include/reactphysics3d/memory/MemoryManager.h b/include/reactphysics3d/memory/MemoryManager.h index cf6505bb..e3e7747c 100644 --- a/include/reactphysics3d/memory/MemoryManager.h +++ b/include/reactphysics3d/memory/MemoryManager.h @@ -104,14 +104,18 @@ class MemoryManager { // Allocate memory of a given type RP3D_FORCE_INLINE void* MemoryManager::allocate(AllocationType allocationType, size_t size) { + void* allocatedMemory = nullptr; + switch (allocationType) { - case AllocationType::Base: return mBaseAllocator->allocate(size); - case AllocationType::Pool: return mPoolAllocator.allocate(size); - case AllocationType::Heap: return mHeapAllocator.allocate(size); - case AllocationType::Frame: return mSingleFrameAllocator.allocate(size); + case AllocationType::Base: allocatedMemory = mBaseAllocator->allocate(size); break; + case AllocationType::Pool: allocatedMemory = mPoolAllocator.allocate(size); break; + case AllocationType::Heap: allocatedMemory = mHeapAllocator.allocate(size); break; + case AllocationType::Frame: allocatedMemory = mSingleFrameAllocator.allocate(size); break; } - return nullptr; + assert(allocatedMemory == nullptr || reinterpret_cast(allocatedMemory) % 16 == 0); + + return allocatedMemory; } // Release previously allocated memory. diff --git a/include/reactphysics3d/memory/PoolAllocator.h b/include/reactphysics3d/memory/PoolAllocator.h index 95f56a1f..c2016143 100644 --- a/include/reactphysics3d/memory/PoolAllocator.h +++ b/include/reactphysics3d/memory/PoolAllocator.h @@ -81,13 +81,16 @@ class PoolAllocator : public MemoryAllocator { /// Number of heaps static const int NB_HEAPS = 128; + /// Minimum unit size + static const size_t MIN_UNIT_SIZE = 16; + /// Maximum memory unit size. An allocation request of a size smaller or equal to /// this size will be handled using the small block allocator. However, for an /// allocation request larger than the maximum block size, the standard malloc() /// will be used. - static const size_t MAX_UNIT_SIZE = 1024; + static const size_t MAX_UNIT_SIZE = NB_HEAPS * MIN_UNIT_SIZE; - /// Size a memory chunk + /// Size of a memory chunk static const size_t BLOCK_SIZE = 16 * MAX_UNIT_SIZE; // -------------------- Attributes -------------------- // diff --git a/include/reactphysics3d/memory/SingleFrameAllocator.h b/include/reactphysics3d/memory/SingleFrameAllocator.h index b53b8a4a..c3b006a5 100644 --- a/include/reactphysics3d/memory/SingleFrameAllocator.h +++ b/include/reactphysics3d/memory/SingleFrameAllocator.h @@ -45,10 +45,6 @@ class SingleFrameAllocator : public MemoryAllocator { // -------------------- Constants -------------------- // - /// Number of frames to wait before shrinking the allocated - /// memory if too much is allocated - static const int NB_FRAMES_UNTIL_SHRINK = 120; - /// Initial size (in bytes) of the single frame allocator size_t INIT_SINGLE_FRAME_ALLOCATOR_NB_BYTES = 1048576; // 1Mb @@ -69,13 +65,6 @@ class SingleFrameAllocator : public MemoryAllocator { /// Pointer to the next available memory location in the buffer size_t mCurrentOffset; - /// Current number of frames since we detected too much memory - /// is allocated - size_t mNbFramesTooMuchAllocated; - - /// True if we need to allocate more memory in the next reset() call - bool mNeedToAllocatedMore; - public : // -------------------- Methods -------------------- // diff --git a/src/memory/HeapAllocator.cpp b/src/memory/HeapAllocator.cpp index 85f06337..9dc92682 100644 --- a/src/memory/HeapAllocator.cpp +++ b/src/memory/HeapAllocator.cpp @@ -175,7 +175,12 @@ void* HeapAllocator::allocate(size_t size) { } // Return a pointer to the memory area inside the unit - return static_cast(reinterpret_cast(currentUnit) + sizeof(MemoryUnitHeader)); + void* allocatedMemory = static_cast(reinterpret_cast(currentUnit) + sizeof(MemoryUnitHeader)); + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(allocatedMemory) % GLOBAL_ALIGNMENT == 0); + + return allocatedMemory; } // Release previously allocated memory. @@ -251,6 +256,9 @@ void HeapAllocator::reserve(size_t sizeToAllocate) { void* memory = mBaseAllocator.allocate(sizeToAllocate + sizeof(MemoryUnitHeader)); assert(memory != nullptr); + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(memory) % GLOBAL_ALIGNMENT == 0); + // Create a new memory unit for the allocated memory MemoryUnitHeader* memoryUnit = new (memory) MemoryUnitHeader(sizeToAllocate, nullptr, mMemoryUnits, false); diff --git a/src/memory/PoolAllocator.cpp b/src/memory/PoolAllocator.cpp index c8fcc680..d8fcfbbe 100644 --- a/src/memory/PoolAllocator.cpp +++ b/src/memory/PoolAllocator.cpp @@ -57,7 +57,7 @@ PoolAllocator::PoolAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(ba // 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) * 8; + mUnitSizes[i] = (i+1) * MIN_UNIT_SIZE; } // Initialize the lookup table that maps the size to allocated to the @@ -116,7 +116,12 @@ void* PoolAllocator::allocate(size_t size) { if (size > MAX_UNIT_SIZE) { // Allocate memory using default allocation - return mBaseAllocator.allocate(size); + void* allocatedMemory = mBaseAllocator.allocate(size); + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(allocatedMemory) % GLOBAL_ALIGNMENT == 0); + + return allocatedMemory; } // Get the index of the heap that will take care of the allocation request @@ -129,7 +134,13 @@ void* PoolAllocator::allocate(size_t size) { // Return a pointer to the memory unit MemoryUnit* unit = mFreeMemoryUnits[indexHeap]; mFreeMemoryUnits[indexHeap] = unit->nextUnit; - return unit; + + void* allocatedMemory = static_cast(unit); + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(allocatedMemory) % GLOBAL_ALIGNMENT == 0); + + return allocatedMemory; } else { // If there is no more free memory units in the corresponding heap @@ -170,8 +181,13 @@ void* PoolAllocator::allocate(size_t size) { mFreeMemoryUnits[indexHeap] = newBlock->memoryUnits->nextUnit; mNbCurrentMemoryBlocks++; + void* allocatedMemory = newBlock->memoryUnits; + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(allocatedMemory) % GLOBAL_ALIGNMENT == 0); + // Return the pointer to the first memory unit of the new allocated block - return newBlock->memoryUnits; + return allocatedMemory; } } diff --git a/src/memory/SingleFrameAllocator.cpp b/src/memory/SingleFrameAllocator.cpp index 8cc1970a..6a87ee42 100644 --- a/src/memory/SingleFrameAllocator.cpp +++ b/src/memory/SingleFrameAllocator.cpp @@ -34,7 +34,7 @@ using namespace reactphysics3d; // Constructor SingleFrameAllocator::SingleFrameAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(baseAllocator), mTotalSizeBytes(INIT_SINGLE_FRAME_ALLOCATOR_NB_BYTES), - mCurrentOffset(0), mNbFramesTooMuchAllocated(0), mNeedToAllocatedMore(false) { + mCurrentOffset(0) { // Allocate a whole block of memory at the beginning mMemoryBufferStart = static_cast(mBaseAllocator.allocate(mTotalSizeBytes)); @@ -48,47 +48,71 @@ SingleFrameAllocator::~SingleFrameAllocator() { mBaseAllocator.release(mMemoryBufferStart, mTotalSizeBytes); } - // Allocate memory of a given size (in bytes) and return a pointer to the -// allocated memory. +// allocated memory. Allocated memory must be 16-bytes aligned. void* SingleFrameAllocator::allocate(size_t size) { // Lock the method with a mutex std::lock_guard lock(mMutex); + // Allocate a little bit more memory to make sure we can return an aligned address + const size_t totalSize = size + GLOBAL_ALIGNMENT; + // Check that there is enough remaining memory in the buffer - if (mCurrentOffset + size > mTotalSizeBytes) { + if (mCurrentOffset + totalSize > mTotalSizeBytes) { - // We need to allocate more memory next time reset() is called - mNeedToAllocatedMore = true; + const size_t previousTotalSizeBytes = mTotalSizeBytes; - // Return default memory allocation - return mBaseAllocator.allocate(size); + // Multiply the total memory to allocate by two + mTotalSizeBytes *= 2; + + // Allocate a whole block of memory + void* allocatedMemory = mBaseAllocator.allocate(mTotalSizeBytes); + assert(allocatedMemory != nullptr); + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(allocatedMemory) % GLOBAL_ALIGNMENT == 0); + + char* newMemoryBufferStart = static_cast(allocatedMemory); + + // Copy the previous memory bloc to new new location + memcpy(newMemoryBufferStart, mMemoryBufferStart, previousTotalSizeBytes); + + // Release the memory allocated at the beginning + mBaseAllocator.release(mMemoryBufferStart, previousTotalSizeBytes); + + mMemoryBufferStart = newMemoryBufferStart; } // Next available memory location void* nextAvailableMemory = mMemoryBufferStart + mCurrentOffset; + // Take care of alignment to make sure that we always return an address to the + // enforce the global alignment of the library + uintptr_t currentAdress = reinterpret_cast(nextAvailableMemory); + + // Calculate the adjustment by masking off the lower bits of the address, to determine how "misaligned" it is. + size_t mask = GLOBAL_ALIGNMENT - 1; + uintptr_t misalignment = currentAdress & mask; + ptrdiff_t alignmentOffset = GLOBAL_ALIGNMENT - misalignment; + + // Compute the aligned address + uintptr_t alignedAdress = currentAdress + alignmentOffset; + nextAvailableMemory = reinterpret_cast(alignedAdress); + // Increment the offset - mCurrentOffset += size; + mCurrentOffset += totalSize; + + // Check that allocated memory is 16-bytes aligned + assert(reinterpret_cast(nextAvailableMemory) % GLOBAL_ALIGNMENT == 0); // Return the next available memory location return nextAvailableMemory; } // Release previously allocated memory. -void SingleFrameAllocator::release(void* pointer, size_t size) { +void SingleFrameAllocator::release(void* /*pointer*/, size_t /*size*/) { - // Lock the method with a mutex - std::lock_guard lock(mMutex); - - // If allocated memory is not within the single frame allocation range - char* p = static_cast(pointer); - if (p < mMemoryBufferStart || p > mMemoryBufferStart + mTotalSizeBytes) { - - // Use default deallocation - mBaseAllocator.release(pointer, size); - } } // Reset the marker of the current allocated memory @@ -97,48 +121,6 @@ void SingleFrameAllocator::reset() { // Lock the method with a mutex std::lock_guard lock(mMutex); - // If too much memory is allocated - if (mCurrentOffset < mTotalSizeBytes / 2) { - - mNbFramesTooMuchAllocated++; - - if (mNbFramesTooMuchAllocated > NB_FRAMES_UNTIL_SHRINK) { - - // Release the memory allocated at the beginning - mBaseAllocator.release(mMemoryBufferStart, mTotalSizeBytes); - - // Divide the total memory to allocate by two - mTotalSizeBytes /= 2; - if (mTotalSizeBytes == 0) mTotalSizeBytes = 1; - - // Allocate a whole block of memory at the beginning - mMemoryBufferStart = static_cast(mBaseAllocator.allocate(mTotalSizeBytes)); - assert(mMemoryBufferStart != nullptr); - - mNbFramesTooMuchAllocated = 0; - } - } - else { - mNbFramesTooMuchAllocated = 0; - } - - // If we need to allocate more memory - if (mNeedToAllocatedMore) { - - // Release the memory allocated at the beginning - mBaseAllocator.release(mMemoryBufferStart, mTotalSizeBytes); - - // Multiply the total memory to allocate by two - mTotalSizeBytes *= 2; - - // Allocate a whole block of memory at the beginning - mMemoryBufferStart = static_cast(mBaseAllocator.allocate(mTotalSizeBytes)); - assert(mMemoryBufferStart != nullptr); - - mNeedToAllocatedMore = false; - mNbFramesTooMuchAllocated = 0; - } - // Reset the current offset at the beginning of the block mCurrentOffset = 0; }