Make sure DefaultAllocator and SingleFrameAllocator returned 16-bytes aligned memory

This commit is contained in:
Daniel Chappuis 2022-03-14 07:26:24 +01:00
parent e0ff5b5fd2
commit ff25e839f3
11 changed files with 122 additions and 92 deletions

View File

@ -6,6 +6,9 @@
### Changed ### 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 ### Removed
### Fixed ### Fixed

View File

@ -1,5 +1,5 @@
# Minimum cmake version required # Minimum cmake version required
cmake_minimum_required(VERSION 3.8) cmake_minimum_required(VERSION 3.10)
# Project configuration # Project configuration
project(ReactPhysics3D VERSION 0.9.0 LANGUAGES CXX) 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) add_library(ReactPhysics3D::reactphysics3d ALIAS reactphysics3d)
# C++11 compiler features # 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) set_target_properties(reactphysics3d PROPERTIES CXX_EXTENSIONS OFF)
# Library headers # Library headers

View File

@ -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 /// Distance threshold to consider that two contact points in a manifold are the same
constexpr decimal SAME_CONTACT_POINT_DISTANCE_THRESHOLD = decimal(0.01); 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 /// Current version of ReactPhysics3D
const std::string RP3D_VERSION = std::string("0.9.0"); const std::string RP3D_VERSION = std::string("0.9.0");

View File

@ -28,15 +28,19 @@
// Libraries // Libraries
#include <reactphysics3d/memory/MemoryAllocator.h> #include <reactphysics3d/memory/MemoryAllocator.h>
#include <reactphysics3d/configuration.h>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <stdlib.h>
/// ReactPhysics3D namespace /// ReactPhysics3D namespace
namespace reactphysics3d { namespace reactphysics3d {
// Class DefaultAllocator // 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 { class DefaultAllocator : public MemoryAllocator {
@ -49,15 +53,33 @@ class DefaultAllocator : public MemoryAllocator {
DefaultAllocator& operator=(DefaultAllocator& allocator) = default; DefaultAllocator& operator=(DefaultAllocator& allocator) = default;
/// Allocate memory of a given size (in bytes) and return a pointer to the /// 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 { 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. /// Release previously allocated memory.
virtual void release(void* pointer, size_t /*size*/) override { 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
} }
}; };

View File

@ -50,7 +50,7 @@ class MemoryAllocator {
MemoryAllocator& operator=(MemoryAllocator& allocator) = default; MemoryAllocator& operator=(MemoryAllocator& allocator) = default;
/// Allocate memory of a given size (in bytes) and return a pointer to the /// 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; virtual void* allocate(size_t size)=0;
/// Release previously allocated memory. /// Release previously allocated memory.

View File

@ -104,14 +104,18 @@ class MemoryManager {
// Allocate memory of a given type // Allocate memory of a given type
RP3D_FORCE_INLINE void* MemoryManager::allocate(AllocationType allocationType, size_t size) { RP3D_FORCE_INLINE void* MemoryManager::allocate(AllocationType allocationType, size_t size) {
void* allocatedMemory = nullptr;
switch (allocationType) { switch (allocationType) {
case AllocationType::Base: return mBaseAllocator->allocate(size); case AllocationType::Base: allocatedMemory = mBaseAllocator->allocate(size); break;
case AllocationType::Pool: return mPoolAllocator.allocate(size); case AllocationType::Pool: allocatedMemory = mPoolAllocator.allocate(size); break;
case AllocationType::Heap: return mHeapAllocator.allocate(size); case AllocationType::Heap: allocatedMemory = mHeapAllocator.allocate(size); break;
case AllocationType::Frame: return mSingleFrameAllocator.allocate(size); case AllocationType::Frame: allocatedMemory = mSingleFrameAllocator.allocate(size); break;
} }
return nullptr; assert(allocatedMemory == nullptr || reinterpret_cast<uintptr_t>(allocatedMemory) % 16 == 0);
return allocatedMemory;
} }
// Release previously allocated memory. // Release previously allocated memory.

View File

@ -81,13 +81,16 @@ class PoolAllocator : public MemoryAllocator {
/// Number of heaps /// Number of heaps
static const int NB_HEAPS = 128; 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 /// 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 /// this size will be handled using the small block allocator. However, for an
/// allocation request larger than the maximum block size, the standard malloc() /// allocation request larger than the maximum block size, the standard malloc()
/// will be used. /// 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; static const size_t BLOCK_SIZE = 16 * MAX_UNIT_SIZE;
// -------------------- Attributes -------------------- // // -------------------- Attributes -------------------- //

View File

@ -45,10 +45,6 @@ class SingleFrameAllocator : public MemoryAllocator {
// -------------------- Constants -------------------- // // -------------------- 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 /// Initial size (in bytes) of the single frame allocator
size_t INIT_SINGLE_FRAME_ALLOCATOR_NB_BYTES = 1048576; // 1Mb 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 /// Pointer to the next available memory location in the buffer
size_t mCurrentOffset; 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 : public :
// -------------------- Methods -------------------- // // -------------------- Methods -------------------- //

View File

@ -175,7 +175,12 @@ void* HeapAllocator::allocate(size_t size) {
} }
// Return a pointer to the memory area inside the unit // Return a pointer to the memory area inside the unit
return static_cast<void*>(reinterpret_cast<unsigned char*>(currentUnit) + sizeof(MemoryUnitHeader)); void* allocatedMemory = static_cast<void*>(reinterpret_cast<unsigned char*>(currentUnit) + sizeof(MemoryUnitHeader));
// Check that allocated memory is 16-bytes aligned
assert(reinterpret_cast<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
return allocatedMemory;
} }
// Release previously allocated memory. // Release previously allocated memory.
@ -251,6 +256,9 @@ void HeapAllocator::reserve(size_t sizeToAllocate) {
void* memory = mBaseAllocator.allocate(sizeToAllocate + sizeof(MemoryUnitHeader)); void* memory = mBaseAllocator.allocate(sizeToAllocate + sizeof(MemoryUnitHeader));
assert(memory != nullptr); assert(memory != nullptr);
// Check that allocated memory is 16-bytes aligned
assert(reinterpret_cast<uintptr_t>(memory) % GLOBAL_ALIGNMENT == 0);
// Create a new memory unit for the allocated memory // Create a new memory unit for the allocated memory
MemoryUnitHeader* memoryUnit = new (memory) MemoryUnitHeader(sizeToAllocate, nullptr, mMemoryUnits, false); MemoryUnitHeader* memoryUnit = new (memory) MemoryUnitHeader(sizeToAllocate, nullptr, mMemoryUnits, false);

View File

@ -57,7 +57,7 @@ PoolAllocator::PoolAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(ba
// Initialize the array that contains the sizes of the memory units that will // Initialize the array that contains the sizes of the memory units that will
// be allocated in each different heap // be allocated in each different heap
for (uint i=0; i < NB_HEAPS; i++) { 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 // 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) { if (size > MAX_UNIT_SIZE) {
// Allocate memory using default allocation // 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<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
return allocatedMemory;
} }
// Get the index of the heap that will take care of the allocation request // 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 // Return a pointer to the memory unit
MemoryUnit* unit = mFreeMemoryUnits[indexHeap]; MemoryUnit* unit = mFreeMemoryUnits[indexHeap];
mFreeMemoryUnits[indexHeap] = unit->nextUnit; mFreeMemoryUnits[indexHeap] = unit->nextUnit;
return unit;
void* allocatedMemory = static_cast<void*>(unit);
// Check that allocated memory is 16-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 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; mFreeMemoryUnits[indexHeap] = newBlock->memoryUnits->nextUnit;
mNbCurrentMemoryBlocks++; mNbCurrentMemoryBlocks++;
void* allocatedMemory = newBlock->memoryUnits;
// Check that allocated memory is 16-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 the pointer to the first memory unit of the new allocated block
return newBlock->memoryUnits; return allocatedMemory;
} }
} }

View File

@ -34,7 +34,7 @@ using namespace reactphysics3d;
// Constructor // Constructor
SingleFrameAllocator::SingleFrameAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(baseAllocator), SingleFrameAllocator::SingleFrameAllocator(MemoryAllocator& baseAllocator) : mBaseAllocator(baseAllocator),
mTotalSizeBytes(INIT_SINGLE_FRAME_ALLOCATOR_NB_BYTES), mTotalSizeBytes(INIT_SINGLE_FRAME_ALLOCATOR_NB_BYTES),
mCurrentOffset(0), mNbFramesTooMuchAllocated(0), mNeedToAllocatedMore(false) { mCurrentOffset(0) {
// Allocate a whole block of memory at the beginning // Allocate a whole block of memory at the beginning
mMemoryBufferStart = static_cast<char*>(mBaseAllocator.allocate(mTotalSizeBytes)); mMemoryBufferStart = static_cast<char*>(mBaseAllocator.allocate(mTotalSizeBytes));
@ -48,47 +48,71 @@ SingleFrameAllocator::~SingleFrameAllocator() {
mBaseAllocator.release(mMemoryBufferStart, mTotalSizeBytes); mBaseAllocator.release(mMemoryBufferStart, mTotalSizeBytes);
} }
// Allocate memory of a given size (in bytes) and return a pointer to the // 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) { void* SingleFrameAllocator::allocate(size_t size) {
// Lock the method with a mutex // Lock the method with a mutex
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> 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 // 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 const size_t previousTotalSizeBytes = mTotalSizeBytes;
mNeedToAllocatedMore = true;
// Return default memory allocation // Multiply the total memory to allocate by two
return mBaseAllocator.allocate(size); 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<uintptr_t>(allocatedMemory) % GLOBAL_ALIGNMENT == 0);
char* newMemoryBufferStart = static_cast<char*>(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 // Next available memory location
void* nextAvailableMemory = mMemoryBufferStart + mCurrentOffset; 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<uintptr_t>(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<void*>(alignedAdress);
// Increment the offset // Increment the offset
mCurrentOffset += size; mCurrentOffset += totalSize;
// Check that allocated memory is 16-bytes aligned
assert(reinterpret_cast<uintptr_t>(nextAvailableMemory) % GLOBAL_ALIGNMENT == 0);
// Return the next available memory location // Return the next available memory location
return nextAvailableMemory; return nextAvailableMemory;
} }
// Release previously allocated memory. // 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<std::mutex> lock(mMutex);
// If allocated memory is not within the single frame allocation range
char* p = static_cast<char*>(pointer);
if (p < mMemoryBufferStart || p > mMemoryBufferStart + mTotalSizeBytes) {
// Use default deallocation
mBaseAllocator.release(pointer, size);
}
} }
// Reset the marker of the current allocated memory // Reset the marker of the current allocated memory
@ -97,48 +121,6 @@ void SingleFrameAllocator::reset() {
// Lock the method with a mutex // Lock the method with a mutex
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> 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<char*>(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<char*>(mBaseAllocator.allocate(mTotalSizeBytes));
assert(mMemoryBufferStart != nullptr);
mNeedToAllocatedMore = false;
mNbFramesTooMuchAllocated = 0;
}
// Reset the current offset at the beginning of the block // Reset the current offset at the beginning of the block
mCurrentOffset = 0; mCurrentOffset = 0;
} }