├── CMakeLists.txt ├── .github └── workflows │ ├── cmake_ubuntu.yml │ ├── cmake_clang.yml │ └── cmake_windows.yml ├── DataTypes.h ├── LICENSE ├── Allocator.h ├── Allocator.cpp ├── .gitignore ├── main.cpp └── README.md /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Example CMake command line to create project build files: 2 | # 3 | # *** Windows *** 4 | # cmake -G "Visual Studio 17 2022" -A Win32 -B Build -S . 5 | # 6 | # *** Linux *** 7 | # cmake -G "Unix Makefiles" -B Build -S . 8 | 9 | # Specify the minimum CMake version required 10 | cmake_minimum_required(VERSION 3.10) 11 | 12 | # Project name and language (C or C++) 13 | project(Allocator VERSION 1.0 LANGUAGES CXX) 14 | 15 | # Collect all .cpp and *.h source files in the current directory 16 | file(GLOB SOURCES "${CMAKE_SOURCE_DIR}/*.cpp" "${CMAKE_SOURCE_DIR}/*.h") 17 | 18 | # Add an executable target 19 | add_executable(AllocatorApp ${SOURCES}) 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/cmake_ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'main' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'main' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest # Use Ubuntu environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Configure CMake 20 | run: cmake -S . -B build # Configure CMake to generate build files in 'Build' directory 21 | 22 | - name: Build 23 | run: cmake --build build # Build the project using CMake 24 | 25 | - name: Run AllocatorApp 26 | run: ./build/AllocatorApp # Run the built executable 27 | -------------------------------------------------------------------------------- /.github/workflows/cmake_clang.yml: -------------------------------------------------------------------------------- 1 | name: Clang 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'main' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'main' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest # Use Ubuntu environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Configure CMake with Clang 20 | run: cmake -S . -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ # Configure CMake with Clang as the compiler 21 | 22 | - name: Build 23 | run: cmake --build build # Build the project using CMake 24 | 25 | - name: Run AllocatorApp 26 | run: ./build/AllocatorApp # Run the built executable 27 | -------------------------------------------------------------------------------- /DataTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef _DATA_TYPES_H 2 | #define _DATA_TYPES_H 3 | 4 | #if WIN32 5 | #include "windows.h" 6 | #else 7 | typedef signed char INT8; 8 | typedef unsigned char UINT8; 9 | typedef signed short INT16; 10 | typedef unsigned short UINT16; 11 | typedef unsigned int UINT32; 12 | typedef int INT32; 13 | typedef char CHAR; 14 | typedef short SHORT; 15 | typedef long LONG; 16 | typedef int INT; 17 | typedef unsigned int UINT; 18 | typedef unsigned long DWORD; 19 | typedef unsigned char BYTE; 20 | typedef unsigned short WORD; 21 | typedef float FLOAT; 22 | typedef double DOUBLE; 23 | 24 | #ifndef NULL 25 | #ifdef __cplusplus 26 | #define NULL 0 27 | #else 28 | #define NULL ((void *)0) 29 | #endif 30 | #endif 31 | 32 | #ifndef FALSE 33 | #define FALSE 0 34 | #endif 35 | 36 | #ifndef TRUE 37 | #define TRUE 1 38 | #endif 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /.github/workflows/cmake_windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'main' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'main' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: windows-latest # Use Windows environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Set up Visual Studio 20 | uses: microsoft/setup-msbuild@v1.1 # Set up Visual Studio environment (MSBuild) 21 | 22 | - name: Configure CMake 23 | run: cmake -S . -B build -G "Visual Studio 17 2022" -A Win32 # Configure CMake for Visual Studio 24 | 25 | - name: Build 26 | run: cmake --build build --config Release # Build the project using CMake with Release configuration 27 | 28 | - name: Run AllocatorApp 29 | run: .\build\Release\AllocatorApp.exe # Run the built executable (adjust path for MSBuild) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David Lafreniere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef __ALLOCATOR_H 2 | #define __ALLOCATOR_H 3 | 4 | #include "DataTypes.h" 5 | #include 6 | 7 | /// @see https://github.com/endurodave/Allocator 8 | /// David Lafreniere 9 | class Allocator 10 | { 11 | public: 12 | /// Constructor 13 | /// @param[in] size - size of the fixed blocks 14 | /// @param[in] objects - maximum number of object. If 0, new blocks are 15 | /// created off the heap as necessary. 16 | /// @param[in] memory - pointer to a block of static memory for allocator or NULL 17 | /// to obtain memory from global heap. If not NULL, the objects argument 18 | /// defines the size of the memory block (size x objects = memory size in bytes). 19 | /// @param[in] name - optional allocator name string. 20 | Allocator(size_t size, UINT objects=0, CHAR* memory = NULL, const CHAR* name=NULL); 21 | 22 | /// Destructor 23 | ~Allocator(); 24 | 25 | /// Get a pointer to a memory block. 26 | /// @param[in] size - size of the block to allocate 27 | /// @return Returns pointer to the block. Otherwise NULL if unsuccessful. 28 | void* Allocate(size_t size); 29 | 30 | /// Return a pointer to the memory pool. 31 | /// @param[in] pBlock - block of memory deallocate (i.e push onto free-list) 32 | void Deallocate(void* pBlock); 33 | 34 | /// Get the allocator name string. 35 | /// @return A pointer to the allocator name or NULL if none was assigned. 36 | const CHAR* GetName() { return m_name; } 37 | 38 | /// Gets the fixed block memory size, in bytes, handled by the allocator. 39 | /// @return The fixed block size in bytes. 40 | size_t GetBlockSize() { return m_blockSize; } 41 | 42 | /// Gets the maximum number of blocks created by the allocator. 43 | /// @return The number of fixed memory blocks created. 44 | UINT GetBlockCount() { return m_blockCnt; } 45 | 46 | /// Gets the number of blocks in use. 47 | /// @return The number of blocks in use by the application. 48 | UINT GetBlocksInUse() { return m_blocksInUse; } 49 | 50 | /// Gets the total number of allocations for this allocator instance. 51 | /// @return The total number of allocations. 52 | UINT GetAllocations() { return m_allocations; } 53 | 54 | /// Gets the total number of deallocations for this allocator instance. 55 | /// @return The total number of deallocations. 56 | UINT GetDeallocations() { return m_deallocations; } 57 | 58 | private: 59 | /// Push a memory block onto head of free-list. 60 | /// @param[in] pMemory - block of memory to push onto free-list 61 | void Push(void* pMemory); 62 | 63 | /// Pop a memory block from head of free-list. 64 | /// @return Returns pointer to the block. Otherwise NULL if unsuccessful. 65 | void* Pop(); 66 | 67 | struct Block 68 | { 69 | Block* pNext; 70 | }; 71 | 72 | enum AllocatorMode { HEAP_BLOCKS, HEAP_POOL, STATIC_POOL }; 73 | 74 | const size_t m_blockSize; 75 | const size_t m_objectSize; 76 | const UINT m_maxObjects; 77 | AllocatorMode m_allocatorMode; 78 | Block* m_pHead; 79 | CHAR* m_pPool; 80 | UINT m_poolIndex; 81 | UINT m_blockCnt; 82 | UINT m_blocksInUse; 83 | UINT m_allocations; 84 | UINT m_deallocations; 85 | const CHAR* m_name; 86 | }; 87 | 88 | // Template class to create external memory pool 89 | template 90 | class AllocatorPool : public Allocator 91 | { 92 | public: 93 | AllocatorPool() : Allocator(sizeof(T), Objects, m_memory) 94 | { 95 | } 96 | private: 97 | CHAR m_memory[sizeof(T) * Objects]; 98 | }; 99 | 100 | // macro to provide header file interface 101 | #define DECLARE_ALLOCATOR \ 102 | public: \ 103 | void* operator new(size_t size) { \ 104 | return _allocator.Allocate(size); \ 105 | } \ 106 | void operator delete(void* pObject) { \ 107 | _allocator.Deallocate(pObject); \ 108 | } \ 109 | private: \ 110 | static Allocator _allocator; 111 | 112 | // macro to provide source file interface 113 | #define IMPLEMENT_ALLOCATOR(class, objects, memory) \ 114 | Allocator class::_allocator(sizeof(class), objects, memory, #class); 115 | 116 | #endif 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Allocator.cpp: -------------------------------------------------------------------------------- 1 | #include "Allocator.h" 2 | #include "DataTypes.h" 3 | #include 4 | #include 5 | 6 | //------------------------------------------------------------------------------ 7 | // Constructor 8 | //------------------------------------------------------------------------------ 9 | Allocator::Allocator(size_t size, UINT objects, CHAR* memory, const CHAR* name) : 10 | m_blockSize(size < sizeof(long*) ? sizeof(long*):size), 11 | m_objectSize(size), 12 | m_maxObjects(objects), 13 | m_pHead(NULL), 14 | m_poolIndex(0), 15 | m_blockCnt(0), 16 | m_blocksInUse(0), 17 | m_allocations(0), 18 | m_deallocations(0), 19 | m_name(name) 20 | { 21 | // If using a fixed memory pool 22 | if (m_maxObjects) 23 | { 24 | // If caller provided an external memory pool 25 | if (memory) 26 | { 27 | m_pPool = memory; 28 | m_allocatorMode = STATIC_POOL; 29 | } 30 | else 31 | { 32 | m_pPool = (CHAR*)new CHAR[m_blockSize * m_maxObjects]; 33 | m_allocatorMode = HEAP_POOL; 34 | } 35 | } 36 | else 37 | m_allocatorMode = HEAP_BLOCKS; 38 | } 39 | 40 | //------------------------------------------------------------------------------ 41 | // Destructor 42 | //------------------------------------------------------------------------------ 43 | Allocator::~Allocator() 44 | { 45 | // If using pool then destroy it, otherwise traverse free-list and 46 | // destroy each individual block 47 | if (m_allocatorMode == HEAP_POOL) 48 | delete [] m_pPool; 49 | else if (m_allocatorMode == HEAP_BLOCKS) 50 | { 51 | while(m_pHead) 52 | delete [] (CHAR*)Pop(); 53 | } 54 | } 55 | 56 | //------------------------------------------------------------------------------ 57 | // Allocate 58 | //------------------------------------------------------------------------------ 59 | void* Allocator::Allocate(size_t size) 60 | { 61 | assert(size <= m_objectSize); 62 | 63 | // If can't obtain existing block then get a new one 64 | void* pBlock = Pop(); 65 | if (!pBlock) 66 | { 67 | // If using a pool method then get block from pool, 68 | // otherwise using dynamic so get block from heap 69 | if (m_maxObjects) 70 | { 71 | // If we have not exceeded the pool maximum 72 | if(m_poolIndex < m_maxObjects) 73 | { 74 | pBlock = (void*)(m_pPool + (m_poolIndex++ * m_blockSize)); 75 | } 76 | else 77 | { 78 | // Get the pointer to the new handler 79 | std::new_handler handler = std::set_new_handler(0); 80 | std::set_new_handler(handler); 81 | 82 | // If a new handler is defined, call it 83 | if (handler) 84 | (*handler)(); 85 | else 86 | assert(0); 87 | } 88 | } 89 | else 90 | { 91 | m_blockCnt++; 92 | pBlock = (void*)new CHAR[m_blockSize]; 93 | } 94 | } 95 | 96 | m_blocksInUse++; 97 | m_allocations++; 98 | 99 | return pBlock; 100 | } 101 | 102 | //------------------------------------------------------------------------------ 103 | // Deallocate 104 | //------------------------------------------------------------------------------ 105 | void Allocator::Deallocate(void* pBlock) 106 | { 107 | Push(pBlock); 108 | m_blocksInUse--; 109 | m_deallocations++; 110 | } 111 | 112 | //------------------------------------------------------------------------------ 113 | // Push 114 | //------------------------------------------------------------------------------ 115 | void Allocator::Push(void* pMemory) 116 | { 117 | Block* pBlock = (Block*)pMemory; 118 | pBlock->pNext = m_pHead; 119 | m_pHead = pBlock; 120 | } 121 | 122 | //------------------------------------------------------------------------------ 123 | // Pop 124 | //------------------------------------------------------------------------------ 125 | void* Allocator::Pop() 126 | { 127 | Block* pBlock = NULL; 128 | 129 | if (m_pHead) 130 | { 131 | pBlock = m_pHead; 132 | m_pHead = m_pHead->pNext; 133 | } 134 | 135 | return (void*)pBlock; 136 | } 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | Build 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # Benchmark Results 47 | BenchmarkDotNet.Artifacts/ 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # Visual Studio Trace Files 101 | *.e2e 102 | 103 | # TFS 2012 Local Workspace 104 | $tf/ 105 | 106 | # Guidance Automation Toolkit 107 | *.gpState 108 | 109 | # ReSharper is a .NET coding add-in 110 | _ReSharper*/ 111 | *.[Rr]e[Ss]harper 112 | *.DotSettings.user 113 | 114 | # JustCode is a .NET coding add-in 115 | .JustCode 116 | 117 | # TeamCity is a build add-in 118 | _TeamCity* 119 | 120 | # DotCover is a Code Coverage Tool 121 | *.dotCover 122 | 123 | # AxoCover is a Code Coverage Tool 124 | .axoCover/* 125 | !.axoCover/settings.json 126 | 127 | # Visual Studio code coverage results 128 | *.coverage 129 | *.coveragexml 130 | 131 | # NCrunch 132 | _NCrunch_* 133 | .*crunch*.local.xml 134 | nCrunchTemp_* 135 | 136 | # MightyMoose 137 | *.mm.* 138 | AutoTest.Net/ 139 | 140 | # Web workbench (sass) 141 | .sass-cache/ 142 | 143 | # Installshield output folder 144 | [Ee]xpress/ 145 | 146 | # DocProject is a documentation generator add-in 147 | DocProject/buildhelp/ 148 | DocProject/Help/*.HxT 149 | DocProject/Help/*.HxC 150 | DocProject/Help/*.hhc 151 | DocProject/Help/*.hhk 152 | DocProject/Help/*.hhp 153 | DocProject/Help/Html2 154 | DocProject/Help/html 155 | 156 | # Click-Once directory 157 | publish/ 158 | 159 | # Publish Web Output 160 | *.[Pp]ublish.xml 161 | *.azurePubxml 162 | # Note: Comment the next line if you want to checkin your web deploy settings, 163 | # but database connection strings (with potential passwords) will be unencrypted 164 | *.pubxml 165 | *.publishproj 166 | 167 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 168 | # checkin your Azure Web App publish settings, but sensitive information contained 169 | # in these scripts will be unencrypted 170 | PublishScripts/ 171 | 172 | # NuGet Packages 173 | *.nupkg 174 | # The packages folder can be ignored because of Package Restore 175 | **/[Pp]ackages/* 176 | # except build/, which is used as an MSBuild target. 177 | !**/[Pp]ackages/build/ 178 | # Uncomment if necessary however generally it will be regenerated when needed 179 | #!**/[Pp]ackages/repositories.config 180 | # NuGet v3's project.json files produces more ignorable files 181 | *.nuget.props 182 | *.nuget.targets 183 | 184 | # Microsoft Azure Build Output 185 | csx/ 186 | *.build.csdef 187 | 188 | # Microsoft Azure Emulator 189 | ecf/ 190 | rcf/ 191 | 192 | # Windows Store app package directories and files 193 | AppPackages/ 194 | BundleArtifacts/ 195 | Package.StoreAssociation.xml 196 | _pkginfo.txt 197 | *.appx 198 | 199 | # Visual Studio cache files 200 | # files ending in .cache can be ignored 201 | *.[Cc]ache 202 | # but keep track of directories ending in .cache 203 | !*.[Cc]ache/ 204 | 205 | # Others 206 | ClientBin/ 207 | ~$* 208 | *~ 209 | *.dbmdl 210 | *.dbproj.schemaview 211 | *.jfm 212 | *.pfx 213 | *.publishsettings 214 | orleans.codegen.cs 215 | 216 | # Since there are multiple workflows, uncomment next line to ignore bower_components 217 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 218 | #bower_components/ 219 | 220 | # RIA/Silverlight projects 221 | Generated_Code/ 222 | 223 | # Backup & report files from converting an old project file 224 | # to a newer Visual Studio version. Backup files are not needed, 225 | # because we have git ;-) 226 | _UpgradeReport_Files/ 227 | Backup*/ 228 | UpgradeLog*.XML 229 | UpgradeLog*.htm 230 | 231 | # SQL Server files 232 | *.mdf 233 | *.ldf 234 | *.ndf 235 | 236 | # Business Intelligence projects 237 | *.rdl.data 238 | *.bim.layout 239 | *.bim_*.settings 240 | 241 | # Microsoft Fakes 242 | FakesAssemblies/ 243 | 244 | # GhostDoc plugin setting file 245 | *.GhostDoc.xml 246 | 247 | # Node.js Tools for Visual Studio 248 | .ntvs_analysis.dat 249 | node_modules/ 250 | 251 | # Typescript v1 declaration files 252 | typings/ 253 | 254 | # Visual Studio 6 build log 255 | *.plg 256 | 257 | # Visual Studio 6 workspace options file 258 | *.opt 259 | 260 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 261 | *.vbw 262 | 263 | # Visual Studio LightSwitch build output 264 | **/*.HTMLClient/GeneratedArtifacts 265 | **/*.DesktopClient/GeneratedArtifacts 266 | **/*.DesktopClient/ModelManifest.xml 267 | **/*.Server/GeneratedArtifacts 268 | **/*.Server/ModelManifest.xml 269 | _Pvt_Extensions 270 | 271 | # Paket dependency manager 272 | .paket/paket.exe 273 | paket-files/ 274 | 275 | # FAKE - F# Make 276 | .fake/ 277 | 278 | # JetBrains Rider 279 | .idea/ 280 | *.sln.iml 281 | 282 | # CodeRush 283 | .cr/ 284 | 285 | # Python Tools for Visual Studio (PTVS) 286 | __pycache__/ 287 | *.pyc 288 | 289 | # Cake - Uncomment if you are using it 290 | # tools/** 291 | # !tools/packages.config 292 | 293 | # Tabs Studio 294 | *.tss 295 | 296 | # Telerik's JustMock configuration file 297 | *.jmconfig 298 | 299 | # BizTalk build output 300 | *.btp.cs 301 | *.btm.cs 302 | *.odx.cs 303 | *.xsd.cs 304 | 305 | # OpenCover UI analysis results 306 | OpenCover/ -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "Allocator.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // @see https://github.com/endurodave/Allocator 7 | 8 | // On VisualStudio, to disable the debug heap for faster performance when using 9 | // the debugger use this option: 10 | // Debugging > Environment _NO_DEBUG_HEAP=1 11 | 12 | class MyClass 13 | { 14 | DECLARE_ALLOCATOR 15 | // remaining class definition 16 | }; 17 | IMPLEMENT_ALLOCATOR(MyClass, 0, 0) 18 | 19 | // Heap blocks mode unlimited with 100 byte blocks 20 | Allocator allocatorHeapBlocks(100); 21 | 22 | // Heap pool mode with 20, 100 byte blocks 23 | Allocator allocatorHeapPool(100, 20); 24 | 25 | // Static pool mode with 20, 100 byte blocks 26 | char staticMemoryPool[100 * 20]; 27 | Allocator allocatorStaticPool(100, 20, staticMemoryPool); 28 | 29 | // Static pool mode with 20 MyClass sized blocks using template 30 | AllocatorPool allocatorStaticPool2; 31 | 32 | // Benchmark allocators 33 | static const int MAX_BLOCKS = 10000; 34 | static const int MAX_BLOCK_SIZE = 4096; 35 | void* memoryPtrs[MAX_BLOCKS]; 36 | void* memoryPtrs2[MAX_BLOCKS]; 37 | AllocatorPool allocatorStaticPoolBenchmark; 38 | Allocator allocatorHeapBlocksBenchmark(MAX_BLOCK_SIZE); 39 | 40 | static void out_of_memory() 41 | { 42 | // new-handler function called by Allocator when pool is out of memory 43 | assert(0); 44 | } 45 | 46 | typedef void* (*AllocFunc)(int size); 47 | typedef void (*DeallocFunc)(void* ptr); 48 | void Benchmark(const char* name, AllocFunc allocFunc, DeallocFunc deallocFunc); 49 | void* AllocHeap(int size); 50 | void DeallocHeap(void* ptr); 51 | void* AllocStaticPool(int size); 52 | void DeallocStaticPool(void* ptr); 53 | void* AllocHeapBlocks(int size); 54 | void DeallocHeapBlocks(void* ptr); 55 | 56 | //------------------------------------------------------------------------------ 57 | // main 58 | //------------------------------------------------------------------------------ 59 | int main(void) 60 | { 61 | std::set_new_handler(out_of_memory); 62 | 63 | // Allocate MyClass using fixed block allocator 64 | MyClass* myClass = new MyClass(); 65 | delete myClass; 66 | 67 | // Allocate 100 bytes in fixed block allocator, then deallocate 68 | void* memory1 = allocatorHeapBlocks.Allocate(100); 69 | allocatorHeapBlocks.Deallocate(memory1); 70 | 71 | void* memory2 = allocatorHeapBlocks.Allocate(100); 72 | allocatorHeapBlocks.Deallocate(memory2); 73 | 74 | void* memory3 = allocatorHeapPool.Allocate(100); 75 | allocatorHeapPool.Deallocate(memory3); 76 | 77 | void* memory4 = allocatorStaticPool.Allocate(100); 78 | allocatorStaticPool.Deallocate(memory4); 79 | 80 | void* memory5 = allocatorStaticPool2.Allocate(sizeof(MyClass)); 81 | allocatorStaticPool2.Deallocate(memory5); 82 | 83 | Benchmark("Heap (Run 1)", AllocHeap, DeallocHeap); 84 | Benchmark("Heap (Run 2)", AllocHeap, DeallocHeap); 85 | Benchmark("Heap (Run 3)", AllocHeap, DeallocHeap); 86 | Benchmark("Static Pool (Run 1)", AllocStaticPool, DeallocStaticPool); 87 | Benchmark("Static Pool (Run 2)", AllocStaticPool, DeallocStaticPool); 88 | Benchmark("Static Pool (Run 3)", AllocStaticPool, DeallocStaticPool); 89 | Benchmark("Heap Blocks (Run 1)", AllocHeapBlocks, DeallocHeapBlocks); 90 | Benchmark("Heap Blocks (Run 2)", AllocHeapBlocks, DeallocHeapBlocks); 91 | Benchmark("Heap Blocks (Run 3)", AllocHeapBlocks, DeallocHeapBlocks); 92 | return 0; 93 | } 94 | 95 | //------------------------------------------------------------------------------ 96 | // AllocHeap 97 | //------------------------------------------------------------------------------ 98 | void* AllocHeap(int size) 99 | { 100 | return new CHAR[size]; 101 | } 102 | 103 | //------------------------------------------------------------------------------ 104 | // DeallocHeap 105 | //------------------------------------------------------------------------------ 106 | void DeallocHeap(void* ptr) 107 | { 108 | delete [] ptr; 109 | } 110 | 111 | //------------------------------------------------------------------------------ 112 | // AllocStaticPool 113 | //------------------------------------------------------------------------------ 114 | void* AllocStaticPool(int size) 115 | { 116 | return allocatorStaticPoolBenchmark.Allocate(size); 117 | } 118 | 119 | //------------------------------------------------------------------------------ 120 | // DeallocStaticPool 121 | //------------------------------------------------------------------------------ 122 | void DeallocStaticPool(void* ptr) 123 | { 124 | allocatorStaticPoolBenchmark.Deallocate(ptr); 125 | } 126 | 127 | //------------------------------------------------------------------------------ 128 | // AllocHeapBlocks 129 | //------------------------------------------------------------------------------ 130 | void* AllocHeapBlocks(int size) 131 | { 132 | return allocatorHeapBlocksBenchmark.Allocate(size); 133 | } 134 | 135 | //------------------------------------------------------------------------------ 136 | // DeallocHeapBlocks 137 | //------------------------------------------------------------------------------ 138 | void DeallocHeapBlocks(void* ptr) 139 | { 140 | allocatorHeapBlocksBenchmark.Deallocate(ptr); 141 | } 142 | 143 | //------------------------------------------------------------------------------ 144 | // Benchmark 145 | //------------------------------------------------------------------------------ 146 | void Benchmark(const char* name, AllocFunc allocFunc, DeallocFunc deallocFunc) 147 | { 148 | #if WIN32 149 | LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds, TotalElapsedMicroseconds= {0}; 150 | LARGE_INTEGER Frequency; 151 | 152 | SetProcessPriorityBoost(GetCurrentProcess(), true); 153 | 154 | QueryPerformanceFrequency(&Frequency); 155 | 156 | // Allocate MAX_BLOCKS blocks MAX_BLOCK_SIZE / 2 sized blocks 157 | QueryPerformanceCounter(&StartingTime); 158 | for (int i=0; i=0; i--) 203 | deallocFunc(memoryPtrs2[i]); 204 | QueryPerformanceCounter(&EndingTime); 205 | ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; 206 | ElapsedMicroseconds.QuadPart *= 1000000; 207 | ElapsedMicroseconds.QuadPart /= Frequency.QuadPart; 208 | std::cout << name << " deallocate time: " << ElapsedMicroseconds.QuadPart << std::endl; 209 | TotalElapsedMicroseconds.QuadPart += ElapsedMicroseconds.QuadPart; 210 | 211 | std::cout << name << " TOTAL TIME: " << TotalElapsedMicroseconds.QuadPart << std::endl; 212 | 213 | SetProcessPriorityBoost(GetCurrentProcess(), false); 214 | #endif 215 | } 216 | 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue) 2 | [![conan Ubuntu](https://github.com/endurodave/Allocator/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_ubuntu.yml) 3 | [![conan Ubuntu](https://github.com/endurodave/Allocator/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_clang.yml) 4 | [![conan Windows](https://github.com/endurodave/Allocator/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_windows.yml) 5 | 6 | # An Efficient C++ Fixed Block Memory Allocator 7 | 8 | A C++ fixed block memory allocator that increases system performance and offers heap fragmentation fault protection. 9 | 10 | Originally published on CodeProject at: An Efficient C++ Fixed Block Memory Allocator 11 | 12 | See A Fixed Block Memory Allocator in C for a C language version of this allocator. 13 | 14 | # Table of Contents 15 | 16 | - [An Efficient C++ Fixed Block Memory Allocator](#an-efficient-c-fixed-block-memory-allocator) 17 | - [Table of Contents](#table-of-contents) 18 | - [Getting Started](#getting-started) 19 | - [References](#references) 20 | - [Introduction](#introduction) 21 | - [Storage Recycling](#storage-recycling) 22 | - [Heap vs. Pool](#heap-vs-pool) 23 | - [Class Design](#class-design) 24 | - [Using the Code](#using-the-code) 25 | - [Run Time](#run-time) 26 | - [Benchmarking](#benchmarking) 27 | - [Allocator Decisions](#allocator-decisions) 28 | - [Debugging Memory Leaks](#debugging-memory-leaks) 29 | - [Error Handling](#error-handling) 30 | - [Limitations](#limitations) 31 | - [Porting Issues](#porting-issues) 32 | - [Reference Articles](#reference-articles) 33 | 34 | 35 | # Getting Started 36 | 37 | [CMake](https://cmake.org/) is used to create the project build files on any platform including Windows and Linux machines. 38 | 39 | 1. Clone the repository. 40 | 2. From the repository root, run the following CMake command: 41 | `cmake -B Build .` 42 | 3. Build and run the project within the `Build` directory. 43 | 44 | # References 45 | 46 | * xallocator - A `malloc`/`free` fixed-block memory allocator replacement. 47 | * stl_allocator - A `std::allocator` compatible fixed-block memory allocator for use with the C++ Standard Library (`std::list`, `std::string`, `std::stringstream`, ...). 48 | 49 | # Introduction 50 | 51 |

Custom fixed block memory allocators are used to solve at least two types of memory related problems. First, global heap allocations/deallocations can be slow and nondeterministic. You never know how long the memory manager is going to take. Secondly, to eliminate the possibility of a memory allocation fault caused by a fragmented heap – a valid concern, especially on mission-critical type systems.

52 | 53 |

Even if the system isn't considered mission-critical, some embedded systems are designed to run for weeks or years without a reboot. Depending on allocation patterns and heap implementation, long-term heap use can lead to heap faults.

54 | 55 |

The typical solution is to statically declare all objects up front and get rid of dynamic allocation altogether. However, static allocation can waste storage, because the objects, even if they're not actively being used, exist and take up space. In addition, implementing a system around dynamic allocation can offer a more natural design architecture, as opposed to statically declaring all objects.

56 | 57 |

Fixed block memory allocators are not a new idea. People have been designing various sorts of custom memory allocators for a long time. What I am presenting here is a simple C++ allocator implementation that I've used successfully on a variety of projects.

58 | 59 |

The solution presented here will:

60 | 61 |
    62 |
  • Be faster than the global heap
  • 63 |
  • Eliminate heap fragmentation memory faults
  • 64 |
  • Require no additional storage overhead (except for a few bytes of static memory)
  • 65 |
  • Be easy to use
  • 66 |
  • Use minimal code space
  • 67 |
68 | 69 |

A simple class that dispenses and reclaims memory will provide all of the aforementioned benefits, as I'll show.

70 | 71 |

After reading this article, be sure to read the follow-on article "Replace malloc/free with a Fast Fixed Block Memory Allocator" to see how Allocator is used to create a really fast malloc() and free() CRT replacement.

72 | 73 | # Storage Recycling 74 | 75 |

The basic philosophy of the memory management scheme is to recycle memory obtained during object allocations. Once storage for an object has been created, it's never returned to the heap. Instead, the memory is recycled, allowing another object of the same type to reuse the space. I've implemented a class called Allocator that expresses the technique.

76 | 77 |

When the application deletes using Allocator, the memory block for a single object is freed for use again but is not actually released back to the memory manager. Freed blocks are retained in a linked list, called the free-list, to be doled out again for another object of the same type. On every allocation request, Allocator first checks the free-list for an existing memory block. Only if none are available is a new one created. Depending on the desired behavior of Allocator, storage comes from either the global heap or a static memory pool with one of three operating modes:

78 | 79 |
    80 |
  1. Heap blocks
  2. 81 |
  3. Heap pool
  4. 82 |
  5. Static pool
  6. 83 |
84 | 85 | # Heap vs. Pool 86 | 87 |

The Allocator class is capable of creating new blocks from the heap or a memory pool whenever the free-list cannot provide an existing one. If the pool is used, you must specify the number of objects up front. Using the total objects, a pool large enough to handle the maximum number of instances is created. Obtaining block memory from the heap, on the other hand, has no such quantity limitations – construct as many new objects as storage permits.

88 | 89 |

The heap blocks mode allocates from the global heap a new memory block for a single object as necessary to fulfill memory requests. A deallocation puts the block into a free list for later reuse. Creating fresh new blocks off the heap when the free-list is empty frees you from having to set an object limit. This approach offers dynamic-like operation since the number of blocks can expand at run-time. The disadvantage is a loss of deterministic execution during block creation.

90 | 91 |

The heap pool mode creates a single pool from the global heap to hold all blocks. The pool is created using operator new when the Allocator object is constructed. Allocator then provides blocks of memory from the pool during allocations.

92 | 93 |

The static pool mode uses a single memory pool, typically located in static memory, to hold all blocks. The static memory pool is not created by Allocator but instead is provided by the user of the class.

94 | 95 |

The heap pool and static pool modes offers consistent allocation execution times because the memory manager is never involved with obtaining individual blocks. This makes a new operation very fast and deterministic.

96 | 97 | # Class Design 98 | 99 |

The class interface is really straightforward. Allocate() returns a pointer to a memory block and Deallocate() frees the memory block for use again. The constructor is responsible for setting the object size and, if necessary, allocating a memory pool.

100 | 101 |

The arguments passed into the class constructor determine where the new blocks will be obtained. The size argument controls the fixed memory block size. The objects argument sets how many blocks are allowed. 0 means get new blocks from the heap as necessary, whereas any other non-zero value indicates using a pool, heap or static, to handle the specified number of instances. The memory argument is a pointer to a optional static memory. If memory is 0 and objects is not 0, Allocator will create a pool from the heap. The static memory pool must be size x objects bytes in size. The name argument optionally gives the allocator a name, which is useful for gather allocator usage metrics.

102 | 103 |
104 | class Allocator
105 | {
106 | public:
107 |     Allocator(size_t size, UINT objects=0, CHAR* memory=NULL, const CHAR* name=NULL);
108 | ...
109 | 110 |

The examples below show how each of the three operational mode is controlled via the constructor arguments.

111 | 112 |
113 | // Heap blocks mode with unlimited 100 byte blocks
114 | Allocator allocatorHeapBlocks(100);
115 | 
116 | // Heap pool mode with 20, 100 byte blocks
117 | Allocator allocatorHeapPool(100, 20);
118 | 
119 | // Static pool mode with 20, 100 byte blocks
120 | char staticMemoryPool[100 * 20];
121 | Allocator allocatorStaticPool(100, 20, staticMemoryPool);
122 | 123 |

To simplify the static pool method somewhat, a simple AllocatorPool<> template class is used. The first template argument is block type and the second argument is the block quantity.

124 | 125 |
126 | // Static pool mode with 20 MyClass sized blocks 
127 | AllocatorPool<MyClass, 20> allocatorStaticPool2;
128 | 129 |

By calling Allocate(), a pointer to a memory block the size of one instance is returned. When obtaining the block, the free-list is checked to determine if a fee block already exists. If so, just unlink the existing block from the free-list and return a pointer to it; otherwise create a new one from the pool/heap.

130 | 131 |
132 | void* memory1 = allocatorHeapBlocks.Allocate(100);
133 | 134 |

Deallocate() just pushes the block address onto a stack. The stack is actually implemented as a singly linked list (the free-list), but the class only adds/removes from the head so the behavior is that of a stack. Using a stack makes the allocation/deallocations really fast. No searching through a list – just push or pop a block and go.

135 | 136 |
137 | allocatorHeapBlocks.Deallocate(memory1);
138 | 139 |

Now comes a handy for linking blocks together in the free-list without consuming any extra storage for the pointers. If, for example, we use the global operator new, storage is allocated first then the constructor is called. The destruction process is just the reverse; destructor is called, then memory freed. After the destructor is executed, but before the storage is released back to the heap, the memory is no longer being utilized by the object and is freed to be used for other things, like a next pointer. Since the Allocator class needs to keep the deleted blocks around, during operator delete we put the list's next pointer in that currently unused object space. When the block is reused by the application, the pointer is no longer needed and will be overwritten by the newly formed object. This way, there is no per-instance storage overhead incurred.

140 | 141 |

Using freed object space as the memory to link blocks together means the object must be large enough to hold a pointer. The code in the constructor initializer list ensures the minimum block size is never below the size of a pointer.

142 | 143 |

The class destructor frees the storage allocated during execution by deleting the memory pool or, if blocks were obtained off the heap, by traversing the free-list and deleting each block. Since the Allocator class is typically used as a class scope static, it will only be called upon termination of the program. For most embedded devices, the application is terminated when someone yanks the power from the system. Obviously, in this case, the destructor is not required.

144 | 145 |

If you're using the heap blocks method, the allocated blocks cannot be freed when the application terminates unless all the instances are checked into the free-list. Therefore, all outstanding objects must be "deleted" before the program ends. Otherwise, you've got yourself a nice memory leak. Which brings up an interesting point. Doesn't Allocator have to track both the free and used blocks? The short answer is no. The long answer is that once a block is given to the application via a pointer, it then becomes the application's responsibility to return that pointer to Allocator by means of a call to Deallocate() before the program ends. This way, we only need to keep track of the freed blocks.

146 | 147 | # Using the Code 148 | 149 |

I wanted Allocator to be extremely easy to use, so I created macros to automate the interface within a client class. The macros provide a static instance of Allocator and two member functions: operator new and operator delete. By overloading the new and delete operators, Allocator intercepts and handles all memory allocation duties for the client class.

150 | 151 |

The DECLARE_ALLOCATOR macro provides the header file interface and should be included within the class declaration like this:

152 | 153 |
154 | #include "Allocator.h"
155 | class MyClass
156 | {
157 |     DECLARE_ALLOCATOR
158 |     // remaining class definition
159 | };
160 | 161 |

The operator new function calls Allocator to create memory for a single instance of the class. After the memory is allocated, by definition, the operator new calls the appropriate constructor for the class. When overloading new only the memory allocation duties can be taken over. The constructor call is guaranteed by the language. Similarly, when deleting an object, the system first calls the destructor for us, and then operator delete is executed. The operator delete uses the Deallocate() function to store the memory block in the free-list.

162 | 163 |

Among C++ programmers, it's relatively common knowledge that when deleting a class using a base pointer, the destructor should be declared virtual. This ensures that when deleting a class, the correct derived destructor is called. What is less apparent, however, is how the virtual destructor changes which class's overloaded operator delete is called.

164 | 165 |

Although not explicitly declared, the operator delete is a static function. As such, it cannot be declared virtual. So at first glance, one would assume that deleting an object with a base pointer couldn't be routed to the correct class. After all, calling an ordinary static function with a base pointer will invoke the base member's version. However, as we know, calling an operator delete first calls the destructor. With a virtual destructor, the call is routed to the derived class. After the class's destructor executes, the operator delete for that derived class is called. So in essence, the overloaded operator delete was routed to the derived class by way of the virtual destructor. Therefore, if deletes using a base pointer are performed, the base class destructor must be declared virtual. Otherwise, the wrong destructor and overloaded operator delete will be called.

166 | 167 |

The IMPLEMENT_ALLOCATOR macro is the source file portion of the interface and should be placed in file scope.

168 | 169 |
170 | IMPLEMENT_ALLOCATOR(MyClass, 0, 0)
171 | 172 |

Once the macros are in place, the caller can create and destroy instances of this class and deleted object stored will be recycled:

173 | 174 |
175 | MyClass* myClass = new MyClass();
176 | delete myClass;
177 | 178 |

Both single and multiple inheritance situations work with the Allocator class. For example, assuming the class Derived inherits from class Base the following code fragment is legal.

179 | 180 |
181 | Base* base = new Derived;
182 | delete base;
183 | 184 | # Run Time 185 | 186 |

At run time, Allocator will initially have no blocks within the free-list, so the first call to Allocate() will get a block from the pool or heap. As execution continues, the system demand for objects of any given allocator instance will fluctuate and new storage will only be allocated when the free-list cannot offer up an existing block. Eventually, the system will settle into some peak number of instances so that each allocation will be obtained from existing blocks instead of the pool/heap.

187 | 188 |

Compared to obtaining all blocks using the memory manager, the class saves a lot of processing power. During allocations, a pointer is just popped off the free-list, making it extremely quick. Deallocations just push a block pointer back onto the list, which is equally as fast.

189 | 190 | # Benchmarking 191 | 192 |

Benchmarking the Allocator performance vs. the global heap on a Windows PC shows just how fast the class is. An basic test of allocating and deallocating 20000 4096 and 2048 sized blocks in a somewhat interleaved fashion tests the speed improvement. See the attached source code for the exact algorithm. 

193 | 194 |

Windows Allocation Times in Milliseconds

195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 |
AllocatorModeRunBenchmark Time (mS)
Global HeapDebug Heap11640
Global HeapDebug Heap21864
Global HeapDebug Heap31855
Global HeapRelease Heap155
Global HeapRelease Heap247
Global HeapRelease Heap347
AllocatorStatic Pool119
AllocatorStatic Pool27
AllocatorStatic Pool37
AllocatorHeap Blocks130
AllocatorHeap Blocks27
AllocatorHeap Blocks37
280 | 281 |

Windows uses a debug heap when executing within the debugger. The debug heap adds extra safety checks slowing its performance. The release heap is much faster as the checks are disabled. The debug heap can be disabled within Visual Studio by setting _NO_DEBUG_HEAP=1 in the Debugging > Environment project option. 

282 | 283 |

The debug global heap is predictably the slowest at about 1.8 seconds. The release heap is much faster at ~50mS. This benchmark test is very simplistic and a more realistic scenario with varying blocks sizes and random new/delete intervals might produce different results. However, the basic point is illustrated nicely; the memory manager is slower than allocator and highly dependent on the platform's implementation.

284 | 285 |

The Allocator running in static pool mode doesn't rely upon the heap. This has a fast execution time of around 7mS once the free-list is populated with blocks. The 19mS on Run 1 accounts for dicing up the fixed memory pool into individual blocks on the first run. 

286 | 287 |

The Allocator running heap blocks mode is just as fast once the free-list is populated with blocks obtained from the heap. Recall that the heap blocks mode relies upon the global heap to get new blocks, but then recycles them into the free-list for later use. Run 1 shows the allocation hit creating the memory blocks at 30mS. Subsequent benchmarks clock in a very fast 7mS since the free-list is fully populated. 

288 | 289 |

As the benchmarking shows, the Allocator is highly efficient and about seven times faster than the Windows global release heap.

290 | 291 |

For comparison on an embedded system, I ran the same tests using Keil running on an ARM STM32F4 CPU at 168MHz. I had to lower the maximum blocks to 500 and the blocks sizes to 32 and 16 bytes due to the constrained resources. Here are the results.

292 | 293 |

ARM STM32F4 Allocation Times in Milliseconds

294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 |
AllocatorModeRunBenchmark Time (mS)
Global HeapRelease111.6
Global HeapRelease 211.6
Global HeapRelease 311.6
AllocatorStatic Pool10.85
AllocatorStatic Pool20.79
AllocatorStatic Pool30.79
AllocatorHeap Blocks11.19
AllocatorHeap Blocks20.79
AllocatorHeap Blocks30.79
361 | 362 |

As the ARM benchmark results show, the Allocator class is about 15 times faster which is quite significant. It should be noted that the benchmark test really aggravated the Keil heap. The benchmark test allocates, in the ARM case, 500 16-byte blocks. Then every other 16-byte block is deleted followed by allocating 500 32-byte blocks. That last group of 500 allocations added 9.2mS to the overall 11.6mS time. What this says is that when the heap gets fragmented, you can expect the memory manager to take longer with non-deterministic times.

363 | 364 | # Allocator Decisions 365 | 366 |

The first decision to make is do you need an allocator at all. If you don't have an execution speed or fault-tolerance requirement for your project, you probably don't need a custom allocator and the global heap will work just fine.

367 | 368 |

On the other hand, if you do require speed and/or fault-tolerance, the allocator can help and the mode of operation depends on your project requirements. An architect for a mission critical design may forbid all use of the global heap. Yet dynamic allocation may lead to a more efficient or elegant design. In this case, you could use the heap blocks mode during debug development to gain memory usage metrics, then for release switch to the static pool method to create statically allocated pools thus eliminating all global heap access. A few compile-time macros switch between the modes.

369 | 370 |

Alternatively, the heap blocks mode may be fine for the application. It does utilize the heap to obtain new blocks, but does prevent heap-fragmentation faults and speeds allocations once the free-list is populated with enough blocks.

371 | 372 |

While not implemented in the source code due to multi-threaded issues outside the scope of this article, it is easy have the Allocator constructor keep a static list of all constructed instances. Run the system for a while, then at some pointer iterate through all allocators and output metrics like block count and name via the GetBlockCount() and GetName() functions. The usage metrics provide information on sizing the fixed memory pool for each allocator when switching over to a memory pool. Always add a few more blocks than maximum measured block count to give the system some added resiliency against the pool running out of memory.

373 | 374 | # Debugging Memory Leaks 375 | 376 |

Debugging memory leaks can be very difficult, mostly because the heap is a black box with no visibility into the types and sizes of objects allocated. With Allocator, memory leaks are a bit easier to find since the Allocator tracks the total block count. Repeatedly output (to the console for example) the GetBlockCount() and GetName() for each allocator instance and comparing the differences should expose the allocator with an ever increasing block count.

377 | 378 | # Error Handling 379 | 380 |

Allocation errors in C++ are typically caught using the new-handler function. If the memory manager faults while attempting to allocate memory off the heap, the user's error-handling function is called via the new-handler function pointer. By assigning the user's function address to the new-handler, the memory manager is able to call a custom error-handling routine. To make the Allocator class's error handling consistent, allocations that exceed the pool's storage capacity also call the function pointed to by new-handler, centralizing all memory allocation faults in one place.

381 | 382 |
383 | static void out_of_memory()
384 | {
385 |     // new-handler function called by Allocator when pool is out of memory
386 |     assert(0);
387 | }
388 | 
389 | int _tmain(int argc, _TCHAR* argv[])
390 | {
391 |     std::set_new_handler(out_of_memory);
392 | ...
393 | 394 | # Limitations 395 | 396 |

The class does not support arrays of objects. An overloaded operator new[] poses a problem for the object recycling method. When this function is called, the size_t argument contains the total memory required for all of the array elements. Creating separate storage for each element is not an option because multiple calls to new don't guarantee that the blocks are within a contiguous space, which an array needs. Since Allocator only handles same size blocks, arrays are not allowed.

397 | 398 | # Porting Issues 399 | 400 |

Allocator calls the new_handler() directly when the static pool runs out of storage, which may not be appropriate for some systems. This implementation assumes the new-handler function will not return, such as an infinite loop trap or assertion, so it makes no sense to call the handler if the function resolves allocation failures by compacting the heap. A fixed pool is being used and no amount of compaction will remedy that.

401 | 402 | # Reference Articles 403 | 404 |

Replace malloc/free with a Fast Fixed Block Memory Allocator  - use Allocator to create a really fast malloc() and free() CRT replacement.

405 | 406 | 407 | 408 | --------------------------------------------------------------------------------