diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp index 7bba1b1d20..32ccd43f9a 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp @@ -11,47 +11,142 @@ using namespace AZ; -#define RECORDING_ENABLED 0 +#define RECORDING_ENABLED 1 #if RECORDING_ENABLED -struct AllocatorOperation -{ - enum OperationType : unsigned int - { - ALLOCATE, - DEALLOCATE, - REALLOCATE, - RESIZE - }; - OperationType m_operationType : 2; - size_t m_size : 46; - size_t m_alignment : 16; - void* m_ptr; - void* m_newptr; // required for resize -}; -static constexpr size_t s_allocationOperationCount = 5 * 1024; -static AZStd::array s_operations = {}; -static uint64_t s_operationCounter = 0; -static AZStd::mutex s_operationsMutex; +#include +#include +#include +#include -AllocatorOperation& GetNextAllocatorOperation() +namespace { - AZStd::scoped_lock lock(s_operationsMutex); - if (s_operationCounter == s_allocationOperationCount) + class DebugAllocator { - FILE* file = nullptr; - fopen_s(&file, "memoryrecordings.bin", "ab"); - if (file) + public: + typedef void* pointer_type; + typedef AZStd::size_t size_type; + typedef AZStd::ptrdiff_t difference_type; + typedef AZStd::false_type allow_memory_leaks; ///< Regular allocators should not leak. + + AZ_FORCE_INLINE pointer_type allocate(size_t byteSize, size_t alignment, int = 0) { - fwrite(&s_operations, sizeof(AllocatorOperation), s_allocationOperationCount, file); - fclose(file); + return AZ_OS_MALLOC(byteSize, alignment); } - s_operationCounter = 0; - } - return s_operations[s_operationCounter++]; -} + AZ_FORCE_INLINE size_type resize(pointer_type, size_type) + { + return 0; + } + AZ_FORCE_INLINE void deallocate(pointer_type ptr, size_type, size_type) + { + AZ_OS_FREE(ptr); + } + }; + struct AllocatorOperation + { + enum OperationType : unsigned int + { + ALLOCATE, + DEALLOCATE + }; + OperationType m_type: 1; + unsigned int m_size : 28; // Can represent up to 256Mb requests + unsigned int m_alignment : 7; // Can represent up to 128 alignment + unsigned int m_recordId : 28; // Can represent up to 256M simultaneous requests, we reuse ids + }; + static AZStd::mutex s_operationsMutex = {}; + + static constexpr size_t s_maxNumberOfAllocationsToRecord = 16384; + static size_t s_numberOfAllocationsRecorded = 0; + static constexpr size_t s_allocationOperationCount = 5 * 1024; + static AZStd::array s_operations = {}; + static uint64_t s_operationCounter = 0; + + static unsigned int s_nextRecordId = 1; + using AllocatorOperationByAddress = AZStd::map, DebugAllocator>; + static AllocatorOperationByAddress s_allocatorOperationByAddress; + using AvailableRecordIds = AZStd::vector; + AvailableRecordIds s_availableRecordIds; + + void RecordAllocatorOperation(AllocatorOperation::OperationType type, void* ptr, size_t size = 0, size_t alignment = 0) + { + AZStd::scoped_lock lock(s_operationsMutex); + if (s_operationCounter == s_allocationOperationCount) + { + AZ::IO::SystemFile file; + file.Open("memoryrecordings.bin", AZ::IO::SystemFile::OpenMode::SF_OPEN_APPEND); + if (file.IsOpen()) + { + file.Write(&s_operations, sizeof(AllocatorOperation) * s_allocationOperationCount); + file.Close(); + } + s_operationCounter = 0; + } + AllocatorOperation& operation = s_operations[s_operationCounter++]; + operation.m_type = type; + if (type == AllocatorOperation::OperationType::ALLOCATE) + { + if (s_numberOfAllocationsRecorded > s_maxNumberOfAllocationsToRecord) + { + // reached limit of allocations, dont record anymore + --s_operationCounter; + return; + } + ++s_numberOfAllocationsRecorded; + operation.m_size = size; + operation.m_alignment = alignment; + unsigned int recordId = 0; + if (!s_availableRecordIds.empty()) + { + recordId = s_availableRecordIds.back(); + s_availableRecordIds.pop_back(); + } + else + { + recordId = s_nextRecordId; + ++s_nextRecordId; + } + operation.m_recordId = recordId; + auto it = s_allocatorOperationByAddress.emplace(ptr, operation); + if (!it.second) + { + // double alloc or resize, leave the current record and return the id + operation = it.first->second; + s_availableRecordIds.emplace_back(recordId); + } + } + else + { + if (ptr == nullptr) + { + // common scenario, just record the operation + operation.m_size = 0; + operation.m_alignment = 0; + operation.m_recordId = 0; // recordId = 0 will flag this case + } + else + { + auto it = s_allocatorOperationByAddress.find(ptr); + if (it != s_allocatorOperationByAddress.end()) + { + operation.m_size = it->second.m_size; + operation.m_alignment = it->second.m_alignment; + operation.m_recordId = it->second.m_recordId; + s_availableRecordIds.push_back(it->second.m_recordId); + s_allocatorOperationByAddress.erase(it); + } + else + { + // just dont record this operation + --s_operationCounter; + } + } + } + + } +} #endif AllocatorBase::AllocatorBase(IAllocatorAllocate* allocationSource, const char* name, const char* desc) : @@ -188,13 +283,7 @@ void AllocatorBase::ProfileAllocation(void* ptr, size_t byteSize, size_t alignme } #if RECORDING_ENABLED - { - AllocatorOperation& op = GetNextAllocatorOperation(); - op.m_operationType = AllocatorOperation::ALLOCATE; - op.m_size = byteSize; - op.m_alignment = alignment; - op.m_ptr = ptr; - } + RecordAllocatorOperation(AllocatorOperation::ALLOCATE, ptr, byteSize, alignment); #endif } @@ -209,13 +298,7 @@ void AllocatorBase::ProfileDeallocation(void* ptr, size_t byteSize, size_t align } } #if RECORDING_ENABLED - { - AllocatorOperation& op = GetNextAllocatorOperation(); - op.m_operationType = AllocatorOperation::DEALLOCATE; - op.m_size = byteSize; - op.m_alignment = alignment; - op.m_ptr = ptr; - } + RecordAllocatorOperation(AllocatorOperation::DEALLOCATE, ptr, byteSize, alignment); #endif } @@ -232,14 +315,8 @@ void AllocatorBase::ProfileReallocationEnd(void* ptr, void* newPtr, size_t newSi ProfileAllocation(newPtr, newSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0); } #if RECORDING_ENABLED - { - AllocatorOperation& op = GetNextAllocatorOperation(); - op.m_operationType = AllocatorOperation::REALLOCATE; - op.m_size = newSize; - op.m_alignment = newAlignment; - op.m_ptr = ptr; - op.m_newptr = newPtr; - } + RecordAllocatorOperation(AllocatorOperation::DEALLOCATE, ptr); + RecordAllocatorOperation(AllocatorOperation::ALLOCATE, newPtr, newSize, newAlignment); #endif } @@ -259,13 +336,7 @@ void AllocatorBase::ProfileResize(void* ptr, size_t newSize) } } #if RECORDING_ENABLED - { - AllocatorOperation& op = GetNextAllocatorOperation(); - op.m_operationType = AllocatorOperation::RESIZE; - op.m_size = newSize; - op.m_alignment = 0; - op.m_ptr = ptr; - } + RecordAllocatorOperation(AllocatorOperation::ALLOCATE, ptr, newSize); #endif } diff --git a/Code/Framework/AzCore/CMakeLists.txt b/Code/Framework/AzCore/CMakeLists.txt index 96ed838ccc..838142f0df 100644 --- a/Code/Framework/AzCore/CMakeLists.txt +++ b/Code/Framework/AzCore/CMakeLists.txt @@ -146,6 +146,11 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PROPERTY COMPILE_DEFINITIONS VALUES AZCORETEST_DLL_NAME=\"$\" ) + ly_add_target_files( + TARGETS AzCore.Tests + FILES ${CMAKE_CURRENT_SOURCE_DIR}/Tests/Memory/AllocatorBenchmarkRecordings.bin + OUTPUT_SUBDIRECTORY Tests/AzCore/Memory + ) endif() diff --git a/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarkRecordings.bin b/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarkRecordings.bin new file mode 100644 index 0000000000..2b587a2304 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarkRecordings.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0f148441ce120b303896618cec364b5afb6f8b911b4785ec6358cfe8467cf7a +size 368640 diff --git a/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp b/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp index 76c3316009..f5a728018c 100644 --- a/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp @@ -9,9 +9,8 @@ #if defined(HAVE_BENCHMARK) #include +#include #include -#include -#include #include #include #include @@ -21,6 +20,7 @@ #include #include #include +#include #include @@ -32,11 +32,9 @@ namespace Benchmark size_t GetMemorySize(void* memory); } - //static AZ::Debug::DrillerManager* s_drillerManager = nullptr; - /// /// Test allocator wrapper that redirects the calls to the passed TAllocator by using AZ::AllocatorInstance. - /// It also creates/destroys the TAllocator type and connects the driller (to reflect what happens at runtime) + /// It also creates/destroys the TAllocator type (to reflect what happens at runtime) /// /// Allocator type to wrap template @@ -46,16 +44,10 @@ namespace Benchmark static void SetUp() { AZ::AllocatorInstance::Create(); - - /*s_drillerManager = AZ::Debug::DrillerManager::Create(); - s_drillerManager->Register(aznew AZ::Debug::MemoryDriller);*/ } static void TearDown() { - /*AZ::Debug::DrillerManager::Destroy(s_drillerManager); - s_drillerManager = nullptr;*/ - AZ::AllocatorInstance::Destroy(); } @@ -410,15 +402,12 @@ namespace Benchmark enum OperationType : unsigned int { ALLOCATE, - DEALLOCATE, - REALLOCATE, - RESIZE + DEALLOCATE }; - OperationType m_operationType : 2; - size_t m_size : 46; - size_t m_alignment : 16; - void* m_ptr; - void* m_newptr; // required for resize + OperationType m_type : 1; + unsigned int m_size : 28; // Can represent up to 256Mb requests + unsigned int m_alignment : 7; // Can represent up to 128 alignment + unsigned int m_recordId : 28; // Can represent up to 256M simultaneous requests, we reuse ids }; public: @@ -428,18 +417,18 @@ namespace Benchmark { state.PauseTiming(); - AZStd::unordered_map pointerRemapping; - AZStd::unordered_map allocationSize; + AZStd::unordered_map pointerRemapping; constexpr size_t allocationOperationCount = 5 * 1024; AZStd::array m_operations = {}; - FILE* file = nullptr; - fopen_s(&file, "memoryrecordings.bin", "rb"); - if (!file) + AZ::IO::SystemFile file; + AZ::IO::FixedMaxPathString filePath = AZ::Utils::GetExecutableDirectory(); + filePath += "/Tests/AzCore/Memory/AllocatorBenchmarkRecordings.bin"; + if (!file.Open(filePath.c_str(), AZ::IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY)) { return; } - size_t elementsRead = fread(&m_operations, sizeof(AllocatorOperation), allocationOperationCount, file); + size_t elementsRead = file.Read(sizeof(AllocatorOperation) * allocationOperationCount, &m_operations); size_t totalElementsRead = elementsRead; const size_t processMemoryBaseline = Platform::GetProcessMemoryUsageBytes(); size_t totalAllocationSize = 0; @@ -449,107 +438,54 @@ namespace Benchmark for (size_t operationIndex = 0; operationIndex < elementsRead; ++operationIndex) { const AllocatorOperation& operation = m_operations[operationIndex]; - switch (operation.m_operationType) + if (operation.m_type == AllocatorOperation::ALLOCATE) { - case AllocatorOperation::ALLOCATE: - { - if (operation.m_ptr) + const auto it = pointerRemapping.emplace(operation.m_recordId, nullptr); + if (it.second) // otherwise already allocated { - const auto it = pointerRemapping.emplace(operation.m_ptr, nullptr); - if (it.second) // otherwise already allocated - { - state.ResumeTiming(); - void* ptr = TestAllocatorType::Allocate(operation.m_size, operation.m_alignment); - state.PauseTiming(); - totalAllocationSize += operation.m_size; - it.first->second = ptr; - allocationSize[ptr] = operation.m_size; - } - else - { - //AZ_Warning("RecordedAllocationBenchmarkFixture", false, "Allocation on %p was already made", operation.m_ptr); - } + state.ResumeTiming(); + void* ptr = TestAllocatorType::Allocate(operation.m_size, operation.m_alignment); + state.PauseTiming(); + totalAllocationSize += operation.m_size; + it.first->second = ptr; } - break; - } - case AllocatorOperation::DEALLOCATE: - { - if (operation.m_ptr) // some deallocate(nullptr) are recorded + else { - const auto ptrIt = pointerRemapping.find(operation.m_ptr); + // Doing a resize, dont account for this memory change, this operation is rare and we dont have + // the size of the previous allocation + state.ResumeTiming(); + TestAllocatorType::Resize(it.first->second, operation.m_size); + state.PauseTiming(); + } + } + else // AllocatorOperation::DEALLOCATE: + { + if (operation.m_recordId) + { + const auto ptrIt = pointerRemapping.find(operation.m_recordId); if (ptrIt != pointerRemapping.end()) { - totalAllocationSize -= allocationSize[ptrIt->second]; + totalAllocationSize -= operation.m_size; state.ResumeTiming(); TestAllocatorType::DeAllocate(ptrIt->second, /*operation.m_size*/ 0); // size is not correct after a resize, a 0 size deals with it state.PauseTiming(); pointerRemapping.erase(ptrIt); } } - else + else // deallocate(nullptr) are recorded { // Just to account of the call of deallocate(nullptr); - // totalAllocationSize -= 0; // No real deallocation happened state.ResumeTiming(); - TestAllocatorType::DeAllocate(operation.m_ptr, /*operation.m_size*/ 0); + TestAllocatorType::DeAllocate(nullptr, /*operation.m_size*/ 0); state.PauseTiming(); } - break; - } - case AllocatorOperation::REALLOCATE: - { - void* ptr = nullptr; - if (operation.m_ptr) - { - AZ_Assert(operation.m_newptr, "Need to consider other cases?"); - const auto ptrIt = pointerRemapping.find(operation.m_ptr); - AZ_Assert(ptrIt != pointerRemapping.end(), "Missing allocation for reallocation"); // In case the recording didnt catch something - ptr = ptrIt->second; - pointerRemapping.erase(ptrIt); - } - AZ_Assert(operation.m_newptr != nullptr, "Reallocation failed in the game"); - const auto it = pointerRemapping.emplace(operation.m_newptr, nullptr); - if (it.second) - { - totalAllocationSize -= allocationSize[ptr]; - state.ResumeTiming(); - void* newPtr = TestAllocatorType::ReAllocate(ptr, operation.m_size, operation.m_alignment); - state.PauseTiming(); - totalAllocationSize += operation.m_size; - it.first->second = newPtr; - allocationSize[newPtr] = operation.m_size; - } - else - { - totalAllocationSize -= allocationSize[ptr]; - state.ResumeTiming(); - TestAllocatorType::DeAllocate(ptr); - state.PauseTiming(); - } - break; - } - case AllocatorOperation::RESIZE: - { - const auto ptrIt = pointerRemapping.find(operation.m_ptr); - AZ_Assert(ptrIt != pointerRemapping.end(), "Missing allocation for resize"); // In case the recording didnt catch something - totalAllocationSize -= allocationSize[ptrIt->second]; - state.ResumeTiming(); - TestAllocatorType::Resize(ptrIt->second, operation.m_size); - state.PauseTiming(); - totalAllocationSize += operation.m_size; - if (operation.m_size == 0) - { - pointerRemapping.erase(ptrIt); - } - break; - } } } - elementsRead = fread(&m_operations, sizeof(AllocatorOperation), allocationOperationCount, file); + elementsRead = file.Read(sizeof(AllocatorOperation) * allocationOperationCount, &m_operations); totalElementsRead += elementsRead; } - fclose(file); + file.Close(); state.counters[s_counterAllocatorMemory] = benchmark::Counter(static_cast(TestAllocatorType::NumAllocatedBytes()), benchmark::Counter::kDefaults); state.counters[s_counterProcessMemory] = benchmark::Counter(static_cast(Platform::GetProcessMemoryUsageBytes() - processMemoryBaseline), benchmark::Counter::kDefaults); @@ -579,6 +515,7 @@ namespace Benchmark static void RecordedRunRanges(benchmark::internal::Benchmark* b) { b->Arg(1); + b->Iterations(100); } // For threaded ranges, run just 200, multi-threaded will already multiply by thread