From a5e799aea188144891f95ab8b0e332837102ee9f Mon Sep 17 00:00:00 2001 From: Mark Oostveen Date: Thu, 15 Aug 2024 16:26:19 +0200 Subject: [PATCH] Remove boost dependencies --- .../Benchmarks/FunctionCallBenchmark.cpp | 2 +- JobSystem/Benchmarks/ScalingBenchmark.cpp | 2 +- JobSystem/Src/CMakeLists.txt | 7 +- JobSystem/Src/JobSystem/AtomicMutex.h | 14 +-- JobSystem/Src/JobSystem/JobSystem.cpp | 74 ++++++++---- JobSystem/Src/JobSystem/JobSystem.h | 18 +-- JobSystem/Src/JobSystem/MemoryPool.h | 112 ++++++++++++++++++ JobSystem/Src/JobSystem/WorkerThread.cpp | 2 +- JobSystem/Src/JobSystem/WorkerThread.h | 10 +- JobSystem/vcpkg.json | 3 - JobSystem/vcpkgSetup.cmake | 2 +- 11 files changed, 187 insertions(+), 59 deletions(-) create mode 100644 JobSystem/Src/JobSystem/MemoryPool.h diff --git a/JobSystem/Benchmarks/FunctionCallBenchmark.cpp b/JobSystem/Benchmarks/FunctionCallBenchmark.cpp index ae39c31..a601ce5 100644 --- a/JobSystem/Benchmarks/FunctionCallBenchmark.cpp +++ b/JobSystem/Benchmarks/FunctionCallBenchmark.cpp @@ -145,7 +145,7 @@ double CallMultiJobHeapWorker() } int runIndex = 0; -JbSystem::mutex printMutex; +JbSystem::Mutex printMutex; void SimpleCallBenchmark() { diff --git a/JobSystem/Benchmarks/ScalingBenchmark.cpp b/JobSystem/Benchmarks/ScalingBenchmark.cpp index 9a52e56..82b843c 100644 --- a/JobSystem/Benchmarks/ScalingBenchmark.cpp +++ b/JobSystem/Benchmarks/ScalingBenchmark.cpp @@ -52,7 +52,7 @@ long long RunBenchmark() auto masterJobs = std::make_shared>(); masterJobs->reserve(MasterJobs); - auto emplaceMutex = std::make_shared(); + auto emplaceMutex = std::make_shared(); auto scheduleJobs = JobSystem::CreateParallelJob( 0, MasterJobs, 1, diff --git a/JobSystem/Src/CMakeLists.txt b/JobSystem/Src/CMakeLists.txt index d74bd04..833f754 100644 --- a/JobSystem/Src/CMakeLists.txt +++ b/JobSystem/Src/CMakeLists.txt @@ -25,9 +25,4 @@ endif() target_include_directories(JobSystem PUBLIC ${JobSystemHeaderDirectory}) set_property(TARGET JobSystem PROPERTY CXX_STANDARD 23) -target_link_libraries(JobSystem PRIVATE Threads::Threads) - -find_package(Boost REQUIRED) -target_include_directories(JobSystem PUBLIC ${Boost_INCLUDE_DIRS}) - -#target_compile_definitions(JobSystem PUBLIC DEBUG) +target_link_libraries(JobSystem PRIVATE Threads::Threads) \ No newline at end of file diff --git a/JobSystem/Src/JobSystem/AtomicMutex.h b/JobSystem/Src/JobSystem/AtomicMutex.h index 1cff738..47634d8 100644 --- a/JobSystem/Src/JobSystem/AtomicMutex.h +++ b/JobSystem/Src/JobSystem/AtomicMutex.h @@ -4,15 +4,15 @@ namespace JbSystem { - class mutex + class Mutex { public: - mutex() : _flag(false) {} - mutex(const mutex&) = delete; - mutex(mutex&&) = delete; - mutex& operator=(const mutex&) = delete; - mutex& operator=(mutex&&) = delete; - ~mutex() noexcept { unlock(); } + Mutex() : _flag(false) {} + Mutex(const Mutex&) = delete; + Mutex(Mutex&&) = delete; + Mutex& operator=(const Mutex&) = delete; + Mutex& operator=(Mutex&&) = delete; + ~Mutex() noexcept { unlock(); } bool try_lock() noexcept { diff --git a/JobSystem/Src/JobSystem/JobSystem.cpp b/JobSystem/Src/JobSystem/JobSystem.cpp index a6d1a3e..b76341f 100644 --- a/JobSystem/Src/JobSystem/JobSystem.cpp +++ b/JobSystem/Src/JobSystem/JobSystem.cpp @@ -1,25 +1,25 @@ #include "JobSystem.h" -#include -#include +#include #include -#include "boost/container/small_vector.hpp" -#include - #include +#include namespace JbSystem { // Thread locals static thread_local std::uint16_t randomWorkerIndex; + // Prevent wild recursion patterns const int maxThreadDepth = 5; + using JobStack = std::array; + static thread_local unsigned int threadDepth = 0; // recursion guard, threads must not be able to infinitely go into scopes - static thread_local boost::container::small_vector - jobStack; // stack of all jobs our current thread is executing + static thread_local JobStack jobStack; // stack of all jobs our current thread is executing + static thread_local uint8_t jobStackSize = 0; // To track the current size of the stack static thread_local bool allowedToLowerQueue = true; static thread_local unsigned int maybeLowerWorkDepth = 0; @@ -27,10 +27,38 @@ namespace JbSystem const int maxOptimizeInCycles = maxThreadDepth * 10; static thread_local int optimizeInCycles = 0; + + void jobStackPushJob(const Job* job) { + if (jobStackSize < maxThreadDepth) { + jobStack[jobStackSize++] = job; + } else { + // Handle stack overflow if necessary + // For example, throw an exception or log an error + } + } + + void jobStackPopJob() { + if (jobStackSize > 0) { + --jobStackSize; + } else { + // Handle stack underflow if necessary + // For example, throw an exception or log an error + } + } + + const Job* jobStackCurrentJob() { + if (jobStackSize > 0) { + return jobStack[jobStackSize - 1]; + } + return nullptr; // No job currently being executed + } + bool JobInStack(const JobId& jobId) { - for (const auto& job : jobStack) + for(uint8_t i = 0; i < jobStackSize; i++) { + const auto& job = jobStack[i]; + if (job->GetId() == jobId) { return true; @@ -41,8 +69,10 @@ namespace JbSystem bool IsProposedJobIgnoredByJobStack(const JobId& proposedJob) { - for (const auto& job : jobStack) + for(uint8_t i = 0; i < jobStackSize; i++) { + const auto& job = jobStack[i]; + if (job->GetIgnoreCallback() == nullptr) { continue; @@ -233,7 +263,7 @@ namespace JbSystem ExecuteJob(JobPriority::Low); // Help complete the remaining jobs wasActive = false; - for (JobSystemWorker& worker : boost::adaptors::reverse(_workers)) + for (JobSystemWorker& worker : _workers | std::ranges::views::reverse) { if (!worker.IsActive()) { @@ -367,9 +397,9 @@ namespace JbSystem struct VoidJobTag { }; - void* location = boost::singleton_pool::malloc(); + void* location = MemoryPool::Get().Alloc(); auto destructorCallback = [](JobSystemVoidJob* const& job) - { boost::singleton_pool::free(job); }; + { MemoryPool::Get().Free(job); }; return new (location) JobSystemVoidJob(function, destructorCallback); } @@ -616,7 +646,7 @@ namespace JbSystem struct FinishedTag { }; - void* location = boost::singleton_pool)>::malloc(); + void* location = MemoryPool>::Get().Alloc(); // Wait for task to complete, allocate boolean on the heap because it's possible that we do not have access to our stack auto* finished = new (location) std::atomic(false); @@ -660,7 +690,7 @@ namespace JbSystem } } - boost::singleton_pool)>::free(finished); + MemoryPool>::Get().Free(finished); threadDepth++; } @@ -920,12 +950,13 @@ namespace JbSystem { assert(!JobInStack(currentJob->GetId())); - jobStack.emplace_back(currentJob); + + jobStackPushJob(currentJob); const IgnoreJobCallback& callback = currentJob->GetIgnoreCallback(); if (callback) { - const std::scoped_lock lock(worker._jobsRequiringIgnoringMutex); + const std::scoped_lock lock(worker._jobsRequiringIgnoringMutex); worker._jobsRequiringIgnoring.emplace(currentJob); } @@ -933,18 +964,11 @@ namespace JbSystem if (callback) { - const std::scoped_lock lock(worker._jobsRequiringIgnoringMutex); + const std::scoped_lock lock(worker._jobsRequiringIgnoringMutex); worker._jobsRequiringIgnoring.erase(currentJob); } - for (size_t i = 0; i < jobStack.size(); i++) - { - if (jobStack.at(i)->GetId() == currentJob->GetId()) - { - jobStack.erase(jobStack.begin() + i); - break; - } - } + jobStackPopJob(); worker.FinishJob(currentJob); } diff --git a/JobSystem/Src/JobSystem/JobSystem.h b/JobSystem/Src/JobSystem/JobSystem.h index 6e16918..c5a25fe 100644 --- a/JobSystem/Src/JobSystem/JobSystem.h +++ b/JobSystem/Src/JobSystem/JobSystem.h @@ -2,8 +2,8 @@ #include "Job.h" #include "WorkerThread.h" +#include "MemoryPool.h" -#include "boost/pool/singleton_pool.hpp" #include #include @@ -249,7 +249,7 @@ namespace JbSystem int _workerCount = 0; std::vector _workers; - JbSystem::mutex _optimizePerformance; + JbSystem::Mutex _optimizePerformance; const int _maxJobExecutionsBeforePerformanceOptimization = 10; std::atomic _jobExecutionsTillOptimization = _maxJobExecutionsBeforePerformanceOptimization; @@ -259,7 +259,7 @@ namespace JbSystem std::atomic _showStats; // Deadlock prevention - JbSystem::mutex _spawnedThreadsMutex; + JbSystem::Mutex _spawnedThreadsMutex; std::unordered_map _spawnedThreadsExecutingIgnoredJobs; }; @@ -269,15 +269,15 @@ namespace JbSystem { using FunctionType = std::remove_const_t>; - void* location = boost::singleton_pool < + void* location = MemoryPool< typename JobSystemWithParametersJob::Tag, - sizeof(JobSystemWithParametersJob)>::malloc(); + JobSystemWithParametersJob>::Get().Alloc(); auto deconstructorCallback = [](JobSystemWithParametersJob* const& job) { job->~JobSystemWithParametersJob(); - boost::singleton_pool< + MemoryPool< typename JobSystemWithParametersJob::Tag, - sizeof(JobSystemWithParametersJob)>::free( + JobSystemWithParametersJob>::Get().Free( job); }; return new (location) JobSystemWithParametersJob( @@ -427,10 +427,10 @@ namespace JbSystem callback->Run(); callback->Free(); dependencies->~vector(); - boost::singleton_pool)>::free(dependencies); + MemoryPool>::Get().Free(dependencies); }; - void* location = boost::singleton_pool)>::malloc(); + void* location = MemoryPool>::Get().Alloc(); auto* jobDependencies = new (location) std::vector({dependencies}); Job* callbackJob = JobSystem::CreateJobWithParams(function, std::forward(args)...); diff --git a/JobSystem/Src/JobSystem/MemoryPool.h b/JobSystem/Src/JobSystem/MemoryPool.h new file mode 100644 index 0000000..c5b03ab --- /dev/null +++ b/JobSystem/Src/JobSystem/MemoryPool.h @@ -0,0 +1,112 @@ +#pragma once + +#include "AtomicMutex.h" + +#include +#include + +#define DEFAULT_MEMPOOL_SIZE 16 + +namespace JbSystem{ + + template struct MemoryPool + { + private: + uint32_t size; + uint32_t capacity; + + // Contains large memory blocks + // This is where the actual memory lives. + std::vector markers; + + // Contains the available memory addresses + // When we need a new memory address, we will + // select the address available at the top of this stack. + T** memstack; + + JbSystem::Mutex mutex; + + public: + MemoryPool(uint32_t size = DEFAULT_MEMPOOL_SIZE) : + size(0), + capacity(std::max(size, (uint32_t)DEFAULT_MEMPOOL_SIZE)) + { + T* new_block = (T*)calloc(capacity, sizeof(T)); + markers.push_back(new_block); + + memstack = (T**)calloc(capacity, sizeof(T*)); + // Fill the stack with the available memory addresses. + for (uint32_t i = 0; i < capacity; i++) + { + memstack[i] = new_block + i; + } + } + + ~MemoryPool() + { + for (uint32_t i = 0; i < markers.size(); i++) + { + free(markers[i]); + } + + free(memstack); + } + + T* Alloc() + { + std::lock_guard lock(mutex); + if (size == capacity) + { + // Free the old stack of addresses + free(memstack); + + // Allocated memory has filled, reallocate memory + memstack = (T**)calloc(2 * capacity, sizeof(T*)); + + T* newBlock = (T*)calloc(capacity, sizeof(T)); + + // Keep track of the large memory blocks for destructor + markers.push_back(newBlock); + + // Record the newly available addresses in the stack + // Note that we don't care about the older addresses + // since they are already allocated and given out. + for (uint32_t i = 0; i < capacity; i++) + { + memstack[capacity + i] = newBlock + i; + } + + capacity *= 2; + } + + T* next = memstack[size++]; + memset(next, 0, sizeof(T)); + return next; + } + + void Free(T* mem) + { + std::lock_guard lock(mutex); + + // mem location is now available + // Add that at the top of the stack + memstack[--size] = mem; + } + + int Size() { + std::lock_guard lock(mutex); + + return size; + } + int Capacity() { + std::lock_guard lock(mutex); + + return capacity; + } + + static MemoryPool& Get(){ + static MemoryPool pool; + return pool; + } + }; +} \ No newline at end of file diff --git a/JobSystem/Src/JobSystem/WorkerThread.cpp b/JobSystem/Src/JobSystem/WorkerThread.cpp index cc9d732..548b4ec 100644 --- a/JobSystem/Src/JobSystem/WorkerThread.cpp +++ b/JobSystem/Src/JobSystem/WorkerThread.cpp @@ -308,7 +308,7 @@ namespace JbSystem { const int& id = jobId.ID(); - const std::scoped_lock lock(_modifyingThread); + const std::scoped_lock lock(_modifyingThread); for (const auto& highPriorityJob : _highPriorityTaskQueue) { if (highPriorityJob->GetId().ID() == id) diff --git a/JobSystem/Src/JobSystem/WorkerThread.h b/JobSystem/Src/JobSystem/WorkerThread.h index 470fa01..cb415c0 100644 --- a/JobSystem/Src/JobSystem/WorkerThread.h +++ b/JobSystem/Src/JobSystem/WorkerThread.h @@ -97,10 +97,10 @@ namespace JbSystem std::atomic _isRunning; std::atomic _isBusy; - JbSystem::mutex _modifyingThread; - JbSystem::mutex _scheduledJobsMutex; - JbSystem::mutex _isRunningMutex; - JbSystem::mutex _jobsRequiringIgnoringMutex; // DeadLock prevention - JbSystem::mutex _pausedJobsMutex; // DeadLock prevention + JbSystem::Mutex _modifyingThread; + JbSystem::Mutex _scheduledJobsMutex; + JbSystem::Mutex _isRunningMutex; + JbSystem::Mutex _jobsRequiringIgnoringMutex; // DeadLock prevention + JbSystem::Mutex _pausedJobsMutex; // DeadLock prevention }; } // namespace JbSystem \ No newline at end of file diff --git a/JobSystem/vcpkg.json b/JobSystem/vcpkg.json index 22bc597..03e8011 100644 --- a/JobSystem/vcpkg.json +++ b/JobSystem/vcpkg.json @@ -4,9 +4,6 @@ "port-version": 1, "description": "A high performance jobsystem", "dependencies": [ - "boost-pool", - "boost-container", - "boost-range", "catch2" ] } diff --git a/JobSystem/vcpkgSetup.cmake b/JobSystem/vcpkgSetup.cmake index 3fe1411..a115953 100644 --- a/JobSystem/vcpkgSetup.cmake +++ b/JobSystem/vcpkgSetup.cmake @@ -6,7 +6,7 @@ include_guard(GLOBAL) macro(DownloadAndSetupVCPKG) # Set the desired version of vcpkg - set(VCPKG_COMMIT_ID "36fb23307e10cc6ffcec566c46c4bb3f567c82c6") + set(VCPKG_COMMIT_ID "1de2026f28ead93ff1773e6e680387643e914ea1") # Define the path where vcpkg will be installed set(ENV{VCPKG_ROOT} "${CMAKE_BINARY_DIR}/vcpkg")