├── tests ├── CMakeLists.txt ├── unit │ ├── UnitTest.cpp │ ├── CMakeLists.txt │ ├── Elements.h │ ├── Concurrency.cpp │ └── Basics.cpp ├── GoogleTest.cmake └── GoogleBenchmark.cmake ├── src ├── CMakeLists.txt └── Mempool.h ├── CMake ├── ConfigGTest.cmake ├── ConfigGBench.cmake ├── DownloadProject.CMakeLists.cmake.in └── DownloadProject.cmake ├── CMakeLists.txt ├── CMakeSettings.json ├── .gitattributes ├── .gitignore └── Readme.md /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.8) 3 | 4 | # Include sub-projects. 5 | add_subdirectory ("unit") 6 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_library(MemPool INTERFACE) 4 | 5 | target_include_directories(MemPool INTERFACE .) 6 | -------------------------------------------------------------------------------- /tests/unit/UnitTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "Mempool.h" 6 | #include "gtest/gtest.h" 7 | 8 | // A test runner. 9 | // For actual tests, see Basics.cpp and Concurrency.cpp 10 | 11 | int main(int argc, char **argv) 12 | { 13 | ::testing::InitGoogleTest(&argc, argv); 14 | auto res = RUN_ALL_TESTS(); 15 | system("pause"); 16 | return res; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | # Add source to this project's executable. 4 | #add_test (UnitTest "UnitTest.cpp" "../src/Mempool.h") 5 | add_executable (UnitTest 6 | "UnitTest.cpp" 7 | "Basics.cpp" 8 | "Concurrency.cpp" 9 | ) 10 | 11 | target_link_libraries(UnitTest MemPool gtest_main) 12 | 13 | # TODO: Add tests and install targets if needed. 14 | set_property(TARGET UnitTest PROPERTY CXX_STANDARD 17) 15 | set_property(TARGET UnitTest PROPERTY CXX_STANDARD_REQUIRED ON) 16 | -------------------------------------------------------------------------------- /tests/GoogleTest.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.2) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | 7 | ExternalProject_Add(googletest 8 | GIT_REPOSITORY https://github.com/google/googletest.git 9 | GIT_TAG master 10 | SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" 11 | BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" 12 | CONFIGURE_COMMAND "" 13 | BUILD_COMMAND "" 14 | INSTALL_COMMAND "" 15 | TEST_COMMAND "" 16 | ) -------------------------------------------------------------------------------- /tests/GoogleBenchmark.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.2) 2 | 3 | project(googlebenchmark-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(googlebenchmark 7 | GIT_REPOSITORY https://github.com/google/benchmark.git 8 | GIT_TAG master 9 | SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googlebenchmark-src" 10 | BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googlebenchmark-build" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "" 15 | ) -------------------------------------------------------------------------------- /CMake/ConfigGTest.cmake: -------------------------------------------------------------------------------- 1 | if (CMAKE_VERSION VERSION_LESS 3.2) 2 | set(UPDATE_DISCONNECTED_IF_AVAILABLE "") 3 | else() 4 | set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") 5 | endif() 6 | 7 | include(DownloadProject) 8 | download_project(PROJ googletest 9 | GIT_REPOSITORY https://github.com/google/googletest.git 10 | GIT_TAG master 11 | ${UPDATE_DISCONNECTED_IF_AVAILABLE} 12 | ) 13 | 14 | add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 15 | 16 | include_directories("${googletest_SOURCE_DIR}/include") 17 | -------------------------------------------------------------------------------- /CMake/ConfigGBench.cmake: -------------------------------------------------------------------------------- 1 | if (CMAKE_VERSION VERSION_LESS 3.2) 2 | set(UPDATE_DISCONNECTED_IF_AVAILABLE "") 3 | else() 4 | set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") 5 | endif() 6 | 7 | set(BENCHMARK_DOWNLOAD_DEPENDENCIES ON) 8 | 9 | include(DownloadProject) 10 | download_project(PROJ googlebenchmark 11 | GIT_REPOSITORY https://github.com/google/benchmark.git 12 | GIT_TAG master 13 | ${UPDATE_DISCONNECTED_IF_AVAILABLE} 14 | ) 15 | 16 | add_subdirectory(${googlebenchmark_SOURCE_DIR} ${googlebenchmark_BINARY_DIR}) 17 | 18 | include_directories("${googlebenchmark_SOURCE_DIR}/include") 19 | -------------------------------------------------------------------------------- /CMake/DownloadProject.CMakeLists.cmake.in: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE or https://github.com/Crascit/DownloadProject for details. 3 | 4 | cmake_minimum_required(VERSION 2.8.2) 5 | 6 | project(${DL_ARGS_PROJ}-download NONE) 7 | 8 | include(ExternalProject) 9 | ExternalProject_Add(${DL_ARGS_PROJ}-download 10 | ${DL_ARGS_UNPARSED_ARGUMENTS} 11 | SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" 12 | BINARY_DIR "${DL_ARGS_BINARY_DIR}" 13 | CONFIGURE_COMMAND "" 14 | BUILD_COMMAND "" 15 | INSTALL_COMMAND "" 16 | TEST_COMMAND "" 17 | ) 18 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : Top-level CMake project file, do global configuration 2 | # and include sub-projects here. 3 | # 4 | cmake_minimum_required (VERSION 3.8) 5 | 6 | project ("MemPool") 7 | 8 | set(CMAKE_MODULE_PATH 9 | ${CMAKE_MODULE_PATH} 10 | ${PROJECT_SOURCE_DIR}/cmake 11 | ) 12 | 13 | include(CTest) 14 | 15 | # Include sub-projects. 16 | add_subdirectory ("src") 17 | add_subdirectory ("tests") 18 | 19 | if (MSVC) 20 | # warning level 4 and all warnings as errors 21 | add_compile_options(/W4 /WX) 22 | else() 23 | # lots of warnings and all warnings as errors 24 | add_compile_options(-Wall -Wextra -pedantic -Werror) 25 | endif() 26 | 27 | #include(ConfigGBench) 28 | #set(BENCHMARK_DOWNLOAD_DEPENDENCIES ON) 29 | 30 | include(ConfigGTest) 31 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 32 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ 8 | "msvc_x64_x64" 9 | ], 10 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 11 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 12 | "cmakeCommandArgs": "", 13 | "buildCommandArgs": "-v", 14 | "ctestCommandArgs": "" 15 | }, 16 | { 17 | "name": "x64-Release", 18 | "generator": "Ninja", 19 | "configurationType": "RelWithDebInfo", 20 | "inheritEnvironments": [ 21 | "msvc_x64_x64" 22 | ], 23 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 24 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 25 | "cmakeCommandArgs": "", 26 | "buildCommandArgs": "-v", 27 | "ctestCommandArgs": "" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /tests/unit/Elements.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMPOOL_TEST_ELEMENTS_H 2 | #define MEMPOOL_TEST_ELEMENTS_H 3 | 4 | // Different kinds of classes to use as MemoryPool<> elements. 5 | 6 | struct DefaultConstructible 7 | { 8 | int I; 9 | std::string S; 10 | 11 | // a non-default constructor to test ability of MemPoool to handle 12 | // non-default-constructible classes. 13 | DefaultConstructible() noexcept 14 | : I(10), S("Default") 15 | {} 16 | }; 17 | 18 | struct CustomConstructible 19 | { 20 | int I; 21 | std::string S; 22 | 23 | // a non-default constructor to test ability of MemPoool to handle 24 | // non-default-constructible classes. 25 | CustomConstructible(int i, std::string s) noexcept 26 | : I(i), S(std::move(s)) 27 | {} 28 | }; 29 | 30 | struct ThrowConstructible 31 | { 32 | // a constructor throwing an excetption 33 | ThrowConstructible() { throw std::runtime_error("Thrown"); } 34 | }; 35 | 36 | 37 | #endif // MEMPOOL_TEST_ELEMENTS_H 38 | -------------------------------------------------------------------------------- /tests/unit/Concurrency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "Mempool.h" 6 | #include "gtest/gtest.h" 7 | #include "Elements.h" 8 | 9 | TEST(Mempool, AllocatesConcurrently) 10 | { 11 | using namespace memory; 12 | using namespace std; 13 | 14 | using Element = size_t; 15 | auto pool = MemoryPool{}; 16 | 17 | auto concurrency = 16; 18 | 19 | // concurrently allocate/deallocate a large number (qty) of elements in several iterations 20 | vector threads; 21 | for(size_t t = 0; t < concurrency; ++t) 22 | { 23 | threads.emplace_back([&] 24 | { 25 | auto qty = 100; 26 | vector v; 27 | v.reserve(qty); 28 | 29 | auto iterations = 50; 30 | 31 | while (iterations --> 0) 32 | { 33 | v.clear(); 34 | for(size_t i = 0; i < qty; ++i) { 35 | auto el = pool.alloc(i); 36 | ASSERT_NE(el, nullptr); 37 | ASSERT_EQ(*el, i); 38 | v.push_back(el); 39 | } 40 | 41 | for(size_t i = 0; i < qty; ++i) { 42 | pool.free(v[i]); 43 | } 44 | } 45 | 46 | }); 47 | } 48 | 49 | for(size_t t = 0; t < concurrency; ++t) 50 | threads[t].join(); 51 | 52 | // everything must be deallocated 53 | ASSERT_EQ(pool.allocated(), 0); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /tests/unit/Basics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "Mempool.h" 6 | #include "gtest/gtest.h" 7 | #include "Elements.h" 8 | 9 | #define DEFAULT_CHUNK_SIZE 65534 10 | 11 | 12 | TEST(Mempool, ConstructsWithDefaultCapacity) 13 | { 14 | using namespace memory; 15 | { 16 | auto pool = MemoryPool {}; 17 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 18 | ASSERT_EQ(pool.chunk_count(), 1); 19 | ASSERT_EQ(pool.allocated(), 0); 20 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 21 | ASSERT_EQ(pool.allocated(), 0); 22 | } 23 | { 24 | auto pool = MemoryPool {}; 25 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 26 | ASSERT_EQ(pool.chunk_count(), 1); 27 | ASSERT_EQ(pool.allocated(), 0); 28 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 29 | ASSERT_EQ(pool.allocated(), 0); 30 | } 31 | { 32 | auto pool = MemoryPool {}; 33 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 34 | ASSERT_EQ(pool.chunk_count(), 1); 35 | ASSERT_EQ(pool.allocated(), 0); 36 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 37 | ASSERT_EQ(pool.allocated(), 0); 38 | } 39 | { 40 | auto pool = MemoryPool {}; 41 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 42 | ASSERT_EQ(pool.chunk_count(), 1); 43 | ASSERT_EQ(pool.allocated(), 0); 44 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 45 | ASSERT_EQ(pool.allocated(), 0); 46 | } 47 | { 48 | auto pool = MemoryPool {}; 49 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 50 | ASSERT_EQ(pool.chunk_count(), 1); 51 | ASSERT_EQ(pool.allocated(), 0); 52 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 53 | ASSERT_EQ(pool.allocated(), 0); 54 | } 55 | { 56 | auto pool = MemoryPool {}; 57 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 58 | ASSERT_EQ(pool.chunk_count(), 1); 59 | ASSERT_EQ(pool.allocated(), 0); 60 | ASSERT_EQ(pool.capacity(), pool.chunk_size()); 61 | ASSERT_EQ(pool.allocated(), 0); 62 | } 63 | } 64 | 65 | TEST(Mempool, ConstructsWithCustomCapacity) 66 | { 67 | size_t capacities[] = { 2, 10, 1'000, 10'000, 100'000, 1'000'000 }; 68 | 69 | // default chunk size 70 | for(auto cap : capacities) 71 | { 72 | auto pool = memory::MemoryPool { cap }; 73 | ASSERT_EQ(pool.chunk_size(), DEFAULT_CHUNK_SIZE); 74 | ASSERT_EQ(pool.chunk_count(), cap/pool.chunk_size() + 1); 75 | ASSERT_EQ(pool.allocated(), 0); 76 | ASSERT_EQ(pool.capacity(), pool.chunk_count() * pool.chunk_size()); 77 | ASSERT_EQ(pool.allocated(), 0); 78 | } 79 | 80 | // custom chunk size 81 | for(auto cap : capacities) 82 | { 83 | auto pool = memory::MemoryPool { cap }; 84 | ASSERT_EQ(pool.chunk_size(), 1024); 85 | ASSERT_EQ(pool.chunk_count(), cap/pool.chunk_size() + 1); 86 | ASSERT_EQ(pool.allocated(), 0); 87 | ASSERT_EQ(pool.capacity(), pool.chunk_count() * pool.chunk_size()); 88 | ASSERT_EQ(pool.allocated(), 0); 89 | } 90 | 91 | } 92 | 93 | TEST(Mempool, SupportsDefaultConstructibleElements) 94 | { 95 | auto pool = memory::MemoryPool {}; 96 | 97 | auto elem = pool.alloc(); 98 | 99 | ASSERT_NE(elem, nullptr); 100 | ASSERT_EQ(elem->I, 10); 101 | ASSERT_EQ(elem->S, "Default"); 102 | pool.free(elem); 103 | ASSERT_EQ(pool.allocated(), 0); 104 | } 105 | 106 | TEST(Mempool, SupportsCustomConstructibleElements) 107 | { 108 | auto pool = memory::MemoryPool {}; 109 | 110 | auto elem = pool.alloc(123, "Custom"); 111 | 112 | ASSERT_NE(elem, nullptr); 113 | ASSERT_EQ(elem->I, 123); 114 | ASSERT_EQ(elem->S, "Custom"); 115 | pool.free(elem); 116 | ASSERT_EQ(pool.allocated(), 0); 117 | } 118 | 119 | TEST(Mempool, HandlesThrowngConstructors) 120 | { 121 | auto pool = memory::MemoryPool {}; 122 | 123 | ASSERT_THROW(pool.alloc(), std::runtime_error); 124 | ASSERT_EQ(pool.allocated(), 0); 125 | } 126 | 127 | TEST(Mempool, AllocatesAndDeallocates) 128 | { 129 | auto pool = memory::MemoryPool{}; 130 | 131 | auto constexpr qty = 1'000'000; 132 | 133 | std::vector v; 134 | v.reserve(qty); 135 | for(size_t i = 0; i < qty; ++i) { 136 | auto el = pool.alloc(); 137 | ASSERT_NE(el, nullptr); 138 | v.push_back(el); 139 | } 140 | 141 | ASSERT_EQ(pool.allocated(), qty); 142 | 143 | for(size_t i = 0; i < qty; ++i) 144 | pool.free(v[i]); 145 | 146 | ASSERT_EQ(pool.allocated(), 0); 147 | } 148 | 149 | TEST(Mempool, DoesNotFailOnFreeingGarbage) 150 | { 151 | auto pool = memory::MemoryPool{}; 152 | 153 | auto ourElement = pool.alloc(); 154 | ASSERT_EQ(pool.allocated(), 1); 155 | 156 | pool.free(nullptr); 157 | ASSERT_EQ(pool.allocated(), 1); 158 | 159 | int garbage; 160 | pool.free(&garbage); 161 | ASSERT_EQ(pool.allocated(), 1); 162 | 163 | pool.free(ourElement); 164 | ASSERT_EQ(pool.allocated(), 0); 165 | } 166 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | DISCLAIMER: This code is a playground, it was not thorougly tested and most probably contains some nasty errors. DO NOT USE IT IN PRODUCTION! 2 | 3 | **Table of Contents** 4 | 5 | 6 | - [MemPool](#mempool) 7 | - [Details](#details) 8 | - [Interface](#interface) 9 | - [How to build and run tests](#how-to-build-and-run-tests) 10 | - [Disclaimer](#disclaimer) 11 | - [Toolset](#toolset) 12 | - [Build steps](#build-steps) 13 | 14 | 15 | 16 | # MemPool 17 | 18 | This header-only C++17 library provides an implementation of 19 | a simple concurent lock-free memory pool of elements of a single type. 20 | The element type is passed as a first template parameter. 21 | 22 | ## Details 23 | 24 | 25 | ### Interface 26 | 27 | ```cpp 28 | template 29 | < typename ElemT 30 | , unsigned short ChunkSize = 65534 31 | , typename Allocator = std::allocator 32 | > 33 | class MemoryPool final 34 | { 35 | public: 36 | // construction 37 | MemoryPool(); 38 | MemoryPool(Allocator& alloc); 39 | explicit MemoryPool(size_t initialCapacity, Allocator& alloc = Allocator()); 40 | 41 | ~MemoryPool(); 42 | 43 | // the pool is not copyable and not movable 44 | MemoryPool(const MemoryPool&) = delete; 45 | MemoryPool& operator=(const MemoryPool&) = delete; 46 | MemoryPool(MemoryPool&& other) = delete; 47 | MemoryPool& operator=(MemoryPool&&) = delete; 48 | 49 | // allocation (concurrent, lock-free[*]) 50 | template [[nodiscard]] 51 | ElemT* alloc(Args&& ...args); 52 | 53 | // deallocation (concurrent, lock-free) 54 | void free(ElemT* elem) noexcept; 55 | 56 | // diagnostics (non-concurrent) 57 | static size_t chunk_size() noexcept; 58 | size_t chunk_count() const noexcept; 59 | size_t capacity() const noexcept; 60 | size_t allocated() const noexcept; 61 | }; 62 | 63 | ``` 64 | 65 | The memory pool is implemented as a single-linked list of fixed size chunks, 66 | where each of them contains a preallocated arena of elements. 67 | The number of elements in each chunk is passed as a second template parameter. 68 | The maximum (and default) chunk size if 65534 elements. 69 | The minimum chunk size is 2 elements, but you should avoid too small chunks 70 | as they have relatively large overhead. 71 | 72 | When the pool's current capacity is exceeded, it automatically grows adding 73 | a new chunk. The pool never shrinks until being destroyed. 74 | 75 | Arena chunks are allocated using a standard allocator. 76 | A custom allocator can be passed as a third template parameter. 77 | 78 | Concurrency is supported on a chunk level by a lock-free free list 79 | (a ring buffer of indices). 80 | 81 | [*] NOTE: allocation of a new chunk for pool growth is not lock-free, 82 | though it should happen not very often, and only once per a new chunk allocation. 83 | 84 | The pool does not require the element type to be default constructible. 85 | The pool's `alloc(Args&&...)` method uses a variadic template to pass constructor 86 | parameters and returns a fully constructed element (or throws). 87 | The pool's `free()` method calls an element't destructor (the destructor must be `noexcept`). 88 | 89 | Example: 90 | 91 | ```cpp 92 | #include "mempool.h" 93 | 94 | class Widget { 95 | public: 96 | explicit Widget(std::string name); 97 | ... 98 | }; 99 | 100 | 101 | ... 102 | 103 | // create a pool with a chunk size 1024, and a capacity reserved for 4096 elements (i.e. 4 chunks) 104 | auto pool = MemoryPool { 4096 }; 105 | 106 | Widget* widget = pool.alloc("Hello World!"s); // will call Widged(std::string) 107 | ... 108 | pool.free(widget); // will call Widget::~Widget(); 109 | 110 | ``` 111 | 112 | --- 113 | 114 | ## How to build and run tests 115 | 116 | The library is header-only and does not require a build. 117 | 118 | Unit tests (Google Test) can be built using CMake. 119 | It seems Google Test is not well integrated with MSBuild yet, hence 120 | no traditional `.vcxproj/.sln` are provided. 121 | 122 | 123 | ### Disclaimer 124 | 125 | > This is my first experince using CMake and Google Test/Benchmark 126 | for Visual Studio, and Visual Studio's support of them is still flaky. 127 | Within a limited time frame for the project, I was not able to make Google 128 | Benchmark to automatically download with dependencies and build in VS, 129 | so the project uses Google Test only.* 130 | 131 | ### Toolset 132 | 133 | The project was created with the Microsoft Visual Studio 2017 v15.9.3. 134 | 135 | Following Visual Studio 20017 components must be installed: 136 | 137 | * Visual C++ tools for CMake and Linux 138 | * Visual C++ for Linux Development 139 | * Windows SDK 10 for Desktop [x86 and x64] 140 | 141 | ### Build steps 142 | 143 | To open the project in Visual Studio, run: 144 | 145 | ```cmd 146 | > cd MemPool 147 | > devenv . 148 | ``` 149 | 150 | or start Visual Studio and use the `FILE -> Open -> Folder` main menu command. 151 | 152 | When project is opened, select the `x64-Release` build configuration. 153 | 154 | To configure the project and make InteliSense working, regenerate CMake 155 | cache when Visual Studio asks it or with the 156 | `CMAKE -> Cache -> Generate [x64-Release] -> MemPool` main menu command. 157 | Note that configuraton will download the Google Test library from 158 | their Gir repository. 159 | 160 | To build the project use the `CMAKE -> [Re]Build All` main menu command, 161 | or right-click on a `CMakeLists.txt` and select `Build` menu command from 162 | a popup menu. 163 | 164 | To run tests, right-click on `tests/unit/UnitTest.cpp` and select 165 | `Set as startup item`. Then run the `UnitTest` with the 166 | `DEBUG -> Start [Without Debugging]` main menu command (F5 or Ctrl-F5). 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /CMake/DownloadProject.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE or https://github.com/Crascit/DownloadProject for details. 3 | # 4 | # MODULE: DownloadProject 5 | # 6 | # PROVIDES: 7 | # download_project( PROJ projectName 8 | # [PREFIX prefixDir] 9 | # [DOWNLOAD_DIR downloadDir] 10 | # [SOURCE_DIR srcDir] 11 | # [BINARY_DIR binDir] 12 | # [QUIET] 13 | # ... 14 | # ) 15 | # 16 | # Provides the ability to download and unpack a tarball, zip file, git repository, 17 | # etc. at configure time (i.e. when the cmake command is run). How the downloaded 18 | # and unpacked contents are used is up to the caller, but the motivating case is 19 | # to download source code which can then be included directly in the build with 20 | # add_subdirectory() after the call to download_project(). Source and build 21 | # directories are set up with this in mind. 22 | # 23 | # The PROJ argument is required. The projectName value will be used to construct 24 | # the following variables upon exit (obviously replace projectName with its actual 25 | # value): 26 | # 27 | # projectName_SOURCE_DIR 28 | # projectName_BINARY_DIR 29 | # 30 | # The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically 31 | # need to be provided. They can be specified if you want the downloaded source 32 | # and build directories to be located in a specific place. The contents of 33 | # projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the 34 | # locations used whether you provide SOURCE_DIR/BINARY_DIR or not. 35 | # 36 | # The DOWNLOAD_DIR argument does not normally need to be set. It controls the 37 | # location of the temporary CMake build used to perform the download. 38 | # 39 | # The PREFIX argument can be provided to change the base location of the default 40 | # values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments 41 | # are provided, then PREFIX will have no effect. The default value for PREFIX is 42 | # CMAKE_BINARY_DIR. 43 | # 44 | # The QUIET option can be given if you do not want to show the output associated 45 | # with downloading the specified project. 46 | # 47 | # In addition to the above, any other options are passed through unmodified to 48 | # ExternalProject_Add() to perform the actual download, patch and update steps. 49 | # The following ExternalProject_Add() options are explicitly prohibited (they 50 | # are reserved for use by the download_project() command): 51 | # 52 | # CONFIGURE_COMMAND 53 | # BUILD_COMMAND 54 | # INSTALL_COMMAND 55 | # TEST_COMMAND 56 | # 57 | # Only those ExternalProject_Add() arguments which relate to downloading, patching 58 | # and updating of the project sources are intended to be used. Also note that at 59 | # least one set of download-related arguments are required. 60 | # 61 | # If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to 62 | # prevent a check at the remote end for changes every time CMake is run 63 | # after the first successful download. See the documentation of the ExternalProject 64 | # module for more information. It is likely you will want to use this option if it 65 | # is available to you. Note, however, that the ExternalProject implementation contains 66 | # bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when 67 | # using the URL download method or when specifying a SOURCE_DIR with no download 68 | # method. Fixes for these have been created, the last of which is scheduled for 69 | # inclusion in CMake 3.8.0. Details can be found here: 70 | # 71 | # https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c 72 | # https://gitlab.kitware.com/cmake/cmake/issues/16428 73 | # 74 | # If you experience build errors related to the update step, consider avoiding 75 | # the use of UPDATE_DISCONNECTED. 76 | # 77 | # EXAMPLE USAGE: 78 | # 79 | # include(DownloadProject) 80 | # download_project(PROJ googletest 81 | # GIT_REPOSITORY https://github.com/google/googletest.git 82 | # GIT_TAG master 83 | # UPDATE_DISCONNECTED 1 84 | # QUIET 85 | # ) 86 | # 87 | # add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 88 | # 89 | #======================================================================================== 90 | 91 | 92 | set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") 93 | 94 | include(CMakeParseArguments) 95 | 96 | function(download_project) 97 | 98 | set(options QUIET) 99 | set(oneValueArgs 100 | PROJ 101 | PREFIX 102 | DOWNLOAD_DIR 103 | SOURCE_DIR 104 | BINARY_DIR 105 | # Prevent the following from being passed through 106 | CONFIGURE_COMMAND 107 | BUILD_COMMAND 108 | INSTALL_COMMAND 109 | TEST_COMMAND 110 | ) 111 | set(multiValueArgs "") 112 | 113 | cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 114 | 115 | # Hide output if requested 116 | if (DL_ARGS_QUIET) 117 | set(OUTPUT_QUIET "OUTPUT_QUIET") 118 | else() 119 | unset(OUTPUT_QUIET) 120 | message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") 121 | endif() 122 | 123 | # Set up where we will put our temporary CMakeLists.txt file and also 124 | # the base point below which the default source and binary dirs will be. 125 | # The prefix must always be an absolute path. 126 | if (NOT DL_ARGS_PREFIX) 127 | set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") 128 | else() 129 | get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE 130 | BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") 131 | endif() 132 | if (NOT DL_ARGS_DOWNLOAD_DIR) 133 | set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") 134 | endif() 135 | 136 | # Ensure the caller can know where to find the source and build directories 137 | if (NOT DL_ARGS_SOURCE_DIR) 138 | set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") 139 | endif() 140 | if (NOT DL_ARGS_BINARY_DIR) 141 | set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") 142 | endif() 143 | set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) 144 | set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) 145 | 146 | # The way that CLion manages multiple configurations, it causes a copy of 147 | # the CMakeCache.txt to be copied across due to it not expecting there to 148 | # be a project within a project. This causes the hard-coded paths in the 149 | # cache to be copied and builds to fail. To mitigate this, we simply 150 | # remove the cache if it exists before we configure the new project. It 151 | # is safe to do so because it will be re-generated. Since this is only 152 | # executed at the configure step, it should not cause additional builds or 153 | # downloads. 154 | file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") 155 | 156 | # Create and build a separate CMake project to carry out the download. 157 | # If we've already previously done these steps, they will not cause 158 | # anything to be updated, so extra rebuilds of the project won't occur. 159 | # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project 160 | # has this set to something not findable on the PATH. 161 | configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" 162 | "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") 163 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" 164 | -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" 165 | . 166 | RESULT_VARIABLE result 167 | ${OUTPUT_QUIET} 168 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 169 | ) 170 | if(result) 171 | message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") 172 | endif() 173 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 174 | RESULT_VARIABLE result 175 | ${OUTPUT_QUIET} 176 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 177 | ) 178 | if(result) 179 | message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") 180 | endif() 181 | 182 | endfunction() 183 | -------------------------------------------------------------------------------- /src/Mempool.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMPOOL_H 2 | #define MEMPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace memory { 14 | 15 | 16 | template 17 | < typename ElemT 18 | , unsigned short ChunkSize = 65534 19 | , typename Allocator = std::allocator 20 | > 21 | class MemoryPool final 22 | { 23 | public: 24 | //--------------------------------------------------------------------- 25 | // Allocates a singe chunk of memory for ChunkSize elements, 26 | // with possibility to expand later. 27 | // Throws std::bad_alloc if cannot allocate required memory. 28 | MemoryPool() 29 | : MemoryPool(Allocator()) 30 | {} 31 | 32 | explicit MemoryPool(Allocator& alloc) 33 | : allocator(allocator) 34 | , arena(reinterpret_cast(std::allocator_traits::allocate(alloc, arena_size))) 35 | , freed(0) 36 | , taken(0) 37 | , count(ChunkSize) 38 | { 39 | std::iota(std::begin(freeList), std::end(freeList), 0); 40 | } 41 | 42 | //--------------------------------------------------------------------- 43 | // Allocates as many chunks as needed to fit the initial size 44 | // Throws std::bad_alloc if cannot allocate required memory. 45 | explicit MemoryPool(size_t initialCapacity, Allocator& allocator = Allocator()) 46 | : MemoryPool(allocator) 47 | { 48 | if (initialCapacity > ChunkSize) 49 | next = std::make_unique(initialCapacity - ChunkSize); 50 | } 51 | 52 | MemoryPool(const MemoryPool&) = delete; 53 | MemoryPool& operator=(const MemoryPool&) = delete; 54 | MemoryPool(MemoryPool&& other) = delete; 55 | MemoryPool& operator=(MemoryPool&&) = delete; 56 | 57 | ~MemoryPool() 58 | { 59 | std::allocator_traits::deallocate(allocator, arena, arena_size); 60 | assert(count == ChunkSize); 61 | } 62 | 63 | //--------------------------------------------------------------------- 64 | // Allocates an element in the pool, using arguments passed into the function 65 | // to call a corresponding constructor. 66 | // Never returns null. 67 | // Throws std::bad_alloc if allocation is failed. 68 | // If constructor throws, the allocated element is returned to the pool 69 | // and the exception is rethrown 70 | template 71 | [[nodiscard]] ElemT* alloc(Args&& ...args) 72 | { 73 | auto elem = allocate(); 74 | if constexpr(std::is_nothrow_constructible_v) 75 | { 76 | return ::new(elem) ElemT(std::forward(args)...); 77 | } 78 | else try { 79 | return ::new(elem) ElemT(std::forward(args)...); 80 | } catch(...) { 81 | free(elem); 82 | throw; 83 | } 84 | } 85 | 86 | //--------------------------------------------------------------------- 87 | // Calls an element's destructor and returns a block of memory into the pool. 88 | // Never throws an excepton. 89 | void free(ElemT* elem) noexcept 90 | { 91 | if (elem == nullptr) return; 92 | 93 | elem->~ElemT(); 94 | deallocate(elem); 95 | } 96 | 97 | //--------------------------------------------------------------------- 98 | // Returns a pool chunk size. 99 | static size_t chunk_size() noexcept 100 | { 101 | return ChunkSize; 102 | } 103 | 104 | //--------------------------------------------------------------------- 105 | // Returns a pool chunk size. 106 | // Use for diagnostic purposes only, as this probe method may return 107 | // only approximate result when accessed concurrently. 108 | size_t chunk_count() const noexcept 109 | { 110 | return 1 + (next ? next->chunk_count() : 0); 111 | } 112 | //--------------------------------------------------------------------- 113 | // Returns a current pool capacity. 114 | // Use for diagnostic purposes only, as this probe method may return 115 | // only approximate result when accessed concurrently. 116 | size_t capacity() const noexcept 117 | { 118 | return ChunkSize + (next ? next->capacity() : 0); 119 | } 120 | 121 | //--------------------------------------------------------------------- 122 | // Returns a current count of allocated elements. 123 | // Use for diagnostic purposes only, as this probe method may return 124 | // only approximate result when accessed concurrently. 125 | size_t allocated() const noexcept 126 | { 127 | return (ChunkSize - count) + (next ? next->allocated() : 0); 128 | } 129 | 130 | private: 131 | using index_type = unsigned short; 132 | 133 | constexpr static auto arena_size = ChunkSize * sizeof(ElemT); 134 | constexpr static auto max_index = std::numeric_limits::max(); 135 | constexpr static auto TAKEN = max_index; 136 | 137 | static_assert(ChunkSize > 1 , "Chunk size is too small"); 138 | static_assert(ChunkSize < max_index , "Chunk size is too big"); 139 | static_assert(std::is_same_v, "Incompatible allocator type"); 140 | static_assert(std::is_nothrow_destructible_v, "Element destructor must be noexcept"); 141 | 142 | Allocator allocator; 143 | ElemT* arena; // could use std::array or std::vector, but they require ElemT be default-constructible 144 | 145 | std::array, ChunkSize> freeList; 146 | std::unique_ptr next; 147 | std::atomic freed, // points to the first free block. Increment to stake a next block aquisition 148 | taken; // points to the first taken block. Increment to stake a next block return 149 | std::atomic count; // provides a lock-free tracking of a current free list capacity 150 | 151 | bool contains(ElemT* elem) const noexcept { return elem >= arena && elem < arena + ChunkSize; } 152 | bool getIndex(ElemT* elem) const noexcept { return std::distance(arena, elem); } 153 | 154 | //--------------------------------------------------------------------- 155 | // Gets an uninitialized block of memory from the pool. 156 | // No ElemT constructors are called, so you must use a placement new() 157 | // on the returned pointr and choose which constructor to call. 158 | // Throws std::bad_alloc if allocation is failed. 159 | [[nodiscard]] ElemT* allocate() 160 | { 161 | // do we have free space in this chunk? If not, pass the request to a next chunk 162 | if (count.load() <= 0) 163 | return allocate_next(); // eligible for TCO 164 | 165 | ElemT* elem = nullptr; 166 | 167 | // try to claim a chunk's capacity for a new spot. 168 | auto prevCount = count--; 169 | if (prevCount > 0) 170 | { 171 | // we are good, stake a next spot to take from a free list 172 | volatile auto pos = advance(taken); // volatile just to prevent optimizing the variable out 173 | 174 | // another thread may not have finished freeing yet, so spin till the index is returned to the free list 175 | index_type index { freeList[pos] }; 176 | while(!freeList.at(pos).compare_exchange_weak(index, TAKEN, std::memory_order_relaxed)){ []{}; }; 177 | 178 | // take the spot 179 | elem = &arena[index]; 180 | } 181 | else { 182 | // no free space left, pass the request to a next chunk then rollback the claimed capacity 183 | elem = allocate_next(); 184 | ++count; 185 | } 186 | 187 | return elem; 188 | } 189 | 190 | //--------------------------------------------------------------------- 191 | // Returns a block of memory into the pool. 192 | // Never throws an excepton. 193 | void deallocate(ElemT* elem) noexcept 194 | { 195 | if (!contains(elem)) 196 | return next ? next->deallocate(elem) : void(); // eligible for TCO 197 | 198 | // TODO: check for double-freeing? 199 | 200 | // stake a next spot to return to a free list 201 | volatile auto pos = advance(freed); 202 | 203 | // another thread may not have finished taking yet, so spin till the index is taken from the free list 204 | // and return the block index into the free list 205 | index_type index = static_cast(std::distance(arena, elem)); 206 | index_type expected_taken = TAKEN; 207 | while (freeList.at(pos).compare_exchange_weak(expected_taken, index, std::memory_order_relaxed)){ []{}; } 208 | 209 | ++count; 210 | } 211 | 212 | //--------------------------------------------------------------------- 213 | // Expands the pool adding a new chunk and allocate an element in the new chunk. 214 | // The pool expansion operation is syncronized for thread safety. 215 | ElemT* allocate_next() 216 | { 217 | // The pool expansion operation is very short and rare, and we never shrink, 218 | // so we can reuse a single local static mutex intead of carrying a separate mutex in every instance 219 | static std::mutex expansion; 220 | 221 | if (!next) { 222 | std::lock_guard _(expansion); 223 | if (!next) next = std::make_unique(); 224 | } 225 | return next->allocate(); // eligible for TCO 226 | } 227 | 228 | //--------------------------------------------------------------------- 229 | // Advances a position (head or tail) in a free list, modulo ChunkSize. 230 | // Returns a previous position which can be updated. 231 | static index_type advance(std::atomic& pos) noexcept 232 | { 233 | // stake a next spot in the free list 234 | auto oldPos = pos++; 235 | 236 | // wrap the new position if needed, taking care of other threads possibly advancing the position too 237 | unsigned short unwrapped = pos; 238 | unsigned short wrapped = unwrapped % ChunkSize; 239 | while (!pos.compare_exchange_weak(unwrapped, wrapped, std::memory_order_relaxed)) 240 | wrapped = unwrapped % ChunkSize; 241 | 242 | // return the previous position, wrapped if needed 243 | return oldPos % ChunkSize; 244 | } 245 | 246 | }; 247 | 248 | } 249 | 250 | 251 | #endif // MEMPOOL_H 252 | --------------------------------------------------------------------------------