{"id":21274155,"url":"https://github.com/endurodave/allocator","last_synced_at":"2026-03-18T01:32:49.339Z","repository":{"id":113549438,"uuid":"108658178","full_name":"endurodave/Allocator","owner":"endurodave","description":"An Efficient C++ Fixed Block Memory Allocator","archived":false,"fork":false,"pushed_at":"2024-11-13T14:44:56.000Z","size":29,"stargazers_count":24,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-11-13T15:36:45.434Z","etag":null,"topics":["cpp","cross-platform","memory-allocator"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endurodave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-28T14:56:34.000Z","updated_at":"2024-11-13T14:45:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"370d5029-9821-4c5f-b949-c80c77b4a7ee","html_url":"https://github.com/endurodave/Allocator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAllocator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAllocator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAllocator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAllocator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endurodave","download_url":"https://codeload.github.com/endurodave/Allocator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225701480,"owners_count":17510542,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cross-platform","memory-allocator"],"created_at":"2024-11-21T09:19:10.286Z","updated_at":"2026-03-18T01:32:49.326Z","avatar_url":"https://github.com/endurodave.png","language":"C++","readme":"![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue)\n[![conan Ubuntu](https://github.com/endurodave/Allocator/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_ubuntu.yml)\n[![conan Ubuntu](https://github.com/endurodave/Allocator/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_clang.yml)\n[![conan Windows](https://github.com/endurodave/Allocator/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/Allocator/actions/workflows/cmake_windows.yml)\n\n# An Efficient C++ Fixed Block Memory Allocator\n\nA C++ fixed block memory allocator that increases system performance and offers heap fragmentation fault protection.\n\nOriginally published on CodeProject at: \u003ca href=\"https://www.codeproject.com/Articles/1083210/An-Efficient-Cplusplus-Fixed-Block-Memory-Allocato\"\u003e\u003cstrong\u003eAn Efficient C++ Fixed Block Memory Allocator\u003c/strong\u003e\u003c/a\u003e\n\nSee \u003ca href=\"https://github.com/endurodave/C_Allocator\"\u003eA Fixed Block Memory Allocator in C\u003c/a\u003e for a C language version of this allocator. \n\n# Table of Contents\n\n- [An Efficient C++ Fixed Block Memory Allocator](#an-efficient-c-fixed-block-memory-allocator)\n- [Table of Contents](#table-of-contents)\n- [Getting Started](#getting-started)\n- [References](#references)\n- [Introduction](#introduction)\n- [Storage Recycling](#storage-recycling)\n- [Heap vs. Pool](#heap-vs-pool)\n- [Class Design](#class-design)\n- [Using the Code](#using-the-code)\n- [Run Time](#run-time)\n- [Benchmarking](#benchmarking)\n- [Allocator Decisions](#allocator-decisions)\n- [Debugging Memory Leaks](#debugging-memory-leaks)\n- [Error Handling](#error-handling)\n- [Limitations](#limitations)\n- [Porting Issues](#porting-issues)\n- [Reference Articles](#reference-articles)\n\n\n# Getting Started\n\n[CMake](https://cmake.org/) is used to create the project build files on any platform including Windows and Linux machines.\n\n1. Clone the repository.\n2. From the repository root, run the following CMake command:   \n   `cmake -B Build .`\n3. Build and run the project within the `Build` directory. \n\n# References\n\n* \u003ca href=\"https://github.com/endurodave/xallocator\"\u003exallocator\u003c/a\u003e - A `malloc`/`free` fixed-block memory allocator replacement. \n* \u003ca href=\"https://github.com/endurodave/stl_allocator\"\u003estl_allocator\u003c/a\u003e - A `std::allocator` compatible fixed-block memory allocator for use with the C++ Standard Library (`std::list`, `std::string`, `std::stringstream`, ...). \n\n# Introduction\n\n\u003cp\u003eCustom 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 \u0026ndash; a valid concern, especially on mission-critical type systems.\u003c/p\u003e\n\n\u003cp\u003eEven if the system isn\u0026#39;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.\u003c/p\u003e\n\n\u003cp\u003eThe 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\u0026#39;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.\u003c/p\u003e\n\n\u003cp\u003eFixed 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\u0026#39;ve used successfully on a variety of projects.\u003c/p\u003e\n\n\u003cp\u003eThe solution presented here will:\u003c/p\u003e\n\n\u003cul\u003e\n\t\u003cli\u003eBe faster than the global heap\u003c/li\u003e\n\t\u003cli\u003eEliminate heap fragmentation memory faults\u003c/li\u003e\n\t\u003cli\u003eRequire no additional storage overhead (except for a few bytes of static memory)\u003c/li\u003e\n\t\u003cli\u003eBe easy to use\u003c/li\u003e\n\t\u003cli\u003eUse minimal code space\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eA simple class that dispenses and reclaims memory will provide all of the aforementioned benefits, as I\u0026#39;ll show.\u003c/p\u003e\n\n\u003cp\u003eAfter reading this article, be sure to read the follow-on article \u0026quot;\u003cb\u003e\u003ca href=\"http://www.codeproject.com/Articles/1084801/Replace-malloc-free-with-a-fast-fixed-block-memory\"\u003eReplace malloc/free with a Fast Fixed Block Memory Allocator\u003c/a\u003e\u003c/b\u003e\u0026quot; to see how \u003ccode\u003eAllocator\u003c/code\u003e is used to create a really fast \u003ccode\u003emalloc()\u003c/code\u003e and \u003ccode\u003efree()\u003c/code\u003e CRT replacement.\u003c/p\u003e\n\n# Storage Recycling\n\n\u003cp\u003eThe basic philosophy of the memory management scheme is to recycle memory obtained during object allocations. Once storage for an object has been created, it\u0026#39;s never returned to the heap. Instead, the memory is recycled, allowing another object of the same type to reuse the space. I\u0026#39;ve implemented a class called \u003ccode\u003eAllocator\u003c/code\u003e that expresses the technique.\u003c/p\u003e\n\n\u003cp\u003eWhen the application deletes using \u003ccode\u003eAllocator\u003c/code\u003e, 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, \u003ccode\u003eAllocator\u003c/code\u003e 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 \u003ccode\u003eAllocator\u003c/code\u003e, storage comes from either the global heap or a \u003ccode\u003estatic\u003c/code\u003e memory pool with one of three operating modes:\u003c/p\u003e\n\n\u003col\u003e\n\t\u003cli\u003eHeap blocks\u003c/li\u003e\n\t\u003cli\u003eHeap pool\u003c/li\u003e\n\t\u003cli\u003eStatic pool\u003c/li\u003e\n\u003c/ol\u003e\n\n# Heap vs. Pool\n\n\u003cp\u003eThe \u003ccode\u003eAllocator\u003c/code\u003e 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 \u0026ndash; construct as many new objects as storage permits.\u003c/p\u003e\n\n\u003cp\u003eThe\u003cem\u003e heap blocks\u003c/em\u003e 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.\u003c/p\u003e\n\n\u003cp\u003eThe \u003cem\u003eheap pool\u003c/em\u003e mode creates a single pool from the global heap to hold all blocks. The pool is created using \u003ccode\u003eoperator new\u003c/code\u003e when the \u003ccode\u003eAllocator\u003c/code\u003e object is constructed. \u003ccode\u003eAllocator\u003c/code\u003e then provides blocks of memory from the pool during allocations.\u003c/p\u003e\n\n\u003cp\u003eThe \u003cem\u003estatic pool\u003c/em\u003e mode uses a single memory pool, typically located in static memory, to hold all blocks. The static memory pool is not created by \u003ccode\u003eAllocator\u003c/code\u003e but instead is provided by the user of the class.\u003c/p\u003e\n\n\u003cp\u003eThe 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.\u003c/p\u003e\n\n# Class Design\n\n\u003cp\u003eThe class interface is really straightforward. \u003ccode\u003eAllocate()\u003c/code\u003e returns a pointer to a memory block and \u003ccode\u003eDeallocate()\u003c/code\u003e frees the memory block for use again. The constructor is responsible for setting the object size and, if necessary, allocating a memory pool.\u003c/p\u003e\n\n\u003cp\u003eThe arguments passed into the class constructor determine where the new blocks will be obtained. The \u003ccode\u003esize\u003c/code\u003e argument controls the fixed memory block size. The \u003ccode\u003eobjects\u003c/code\u003e argument sets how many blocks are allowed. \u003ccode\u003e0 \u003c/code\u003emeans 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 \u003ccode\u003ememory\u003c/code\u003e argument is a pointer to a optional \u003ccode\u003estatic\u003c/code\u003e memory. If \u003ccode\u003ememory\u003c/code\u003e is \u003ccode\u003e0\u003c/code\u003e and \u003ccode\u003eobjects\u003c/code\u003e is not \u003ccode\u003e0\u003c/code\u003e, \u003ccode\u003eAllocator\u003c/code\u003e will create a pool from the heap. The static memory pool must be \u003ccode\u003esize\u003c/code\u003e x \u003ccode\u003eobjects\u003c/code\u003e bytes in size. The \u003ccode\u003ename\u003c/code\u003e argument optionally gives the allocator a name, which is useful for gather allocator usage metrics.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nclass Allocator\n{\npublic:\n    Allocator(size_t size, UINT objects=0, CHAR* memory=NULL, const CHAR* name=NULL);\n...\u003c/pre\u003e\n\n\u003cp\u003eThe examples below show how each of the three operational mode is controlled via the constructor arguments.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\n// Heap blocks mode with unlimited 100 byte blocks\nAllocator allocatorHeapBlocks(100);\n\n// Heap pool mode with 20, 100 byte blocks\nAllocator allocatorHeapPool(100, 20);\n\n// Static pool mode with 20, 100 byte blocks\nchar staticMemoryPool[100 * 20];\nAllocator allocatorStaticPool(100, 20, staticMemoryPool);\u003c/pre\u003e\n\n\u003cp\u003eTo simplify the \u003ccode\u003estatic\u003c/code\u003e pool method somewhat, a simple \u003ccode\u003eAllocatorPool\u0026lt;\u0026gt;\u003c/code\u003e template class is used. The first template argument is block type and the second argument is the block quantity.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\n// Static pool mode with 20 MyClass sized blocks \nAllocatorPool\u0026lt;MyClass, 20\u0026gt; allocatorStaticPool2;\u003c/pre\u003e\n\n\u003cp\u003eBy calling \u003ccode\u003eAllocate()\u003c/code\u003e, 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.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid* memory1 = allocatorHeapBlocks.Allocate(100);\u003c/pre\u003e\n\n\u003cp\u003e\u003ccode\u003eDeallocate()\u003c/code\u003e 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 \u0026ndash; just push or pop a block and go.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nallocatorHeapBlocks.Deallocate(memory1);\u003c/pre\u003e\n\n\u003cp\u003eNow 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 \u003ccode\u003eoperator new\u003c/code\u003e, 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 \u003ccode\u003eAllocator\u003c/code\u003e class needs to keep the deleted blocks around, during \u003ccode\u003eoperator delete\u003c/code\u003e we put the list\u0026#39;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.\u003c/p\u003e\n\n\u003cp\u003eUsing 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.\u003c/p\u003e\n\n\u003cp\u003eThe 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 \u003ccode\u003eAllocator\u003c/code\u003e class is typically used as a class scope \u003ccode\u003estatic\u003c/code\u003e, 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.\u003c/p\u003e\n\n\u003cp\u003eIf you\u0026#39;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 \u0026quot;deleted\u0026quot; before the program ends. Otherwise, you\u0026#39;ve got yourself a nice memory leak. Which brings up an interesting point. Doesn\u0026#39;t \u003ccode\u003eAllocator\u003c/code\u003e 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\u0026#39;s responsibility to return that pointer to \u003ccode\u003eAllocator\u003c/code\u003e by means of a call to \u003ccode\u003eDeallocate()\u003c/code\u003e before the program ends. This way, we only need to keep track of the freed blocks.\u003c/p\u003e\n\n# Using the Code\n\n\u003cp\u003eI wanted \u003ccode\u003eAllocator\u003c/code\u003e to be extremely easy to use, so I created macros to automate the interface within a client class. The macros provide a \u003ccode\u003estatic\u003c/code\u003e instance of \u003ccode\u003eAllocator\u003c/code\u003e and two member functions: \u003ccode\u003eoperator new\u003c/code\u003e and \u003ccode\u003eoperator delete\u003c/code\u003e. By overloading the \u003ccode\u003enew\u003c/code\u003e and \u003ccode\u003edelete\u003c/code\u003e operators, \u003ccode\u003eAllocator\u003c/code\u003e intercepts and handles all memory allocation duties for the client class.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eDECLARE_ALLOCATOR\u003c/code\u003e macro provides the header file interface and should be included within the class declaration like this:\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\n#include \u0026quot;Allocator.h\u0026quot;\nclass MyClass\n{\n    DECLARE_ALLOCATOR\n    // remaining class definition\n};\u003c/pre\u003e\n\n\u003cp\u003eThe \u003ccode\u003eoperator new\u003c/code\u003e function calls \u003ccode\u003eAllocator\u003c/code\u003e to create memory for a single instance of the class. After the memory is allocated, by definition, the \u003ccode\u003eoperator new\u003c/code\u003e calls the appropriate constructor for the class. When overloading \u003ccode\u003enew\u003c/code\u003e 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 \u003ccode\u003eoperator delete\u003c/code\u003e is executed. The \u003ccode\u003eoperator delete\u003c/code\u003e uses the \u003ccode\u003eDeallocate()\u003c/code\u003e function to store the memory block in the free-list.\u003c/p\u003e\n\n\u003cp\u003eAmong C++ programmers, it\u0026#39;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\u0026#39;s overloaded \u003ccode\u003eoperator delete\u003c/code\u003e is called.\u003c/p\u003e\n\n\u003cp\u003eAlthough not explicitly declared, the \u003ccode\u003eoperator delete\u003c/code\u003e is a \u003ccode\u003estatic\u003c/code\u003e function. As such, it cannot be declared virtual. So at first glance, one would assume that deleting an object with a base pointer couldn\u0026#39;t be routed to the correct class. After all, calling an ordinary \u003ccode\u003estatic\u003c/code\u003e function with a base pointer will invoke the base member\u0026#39;s version. However, as we know, calling an \u003ccode\u003eoperator delete\u003c/code\u003e first calls the destructor. With a virtual destructor, the call is routed to the derived class. After the class\u0026#39;s destructor executes, the \u003ccode\u003eoperator delete\u003c/code\u003e for that derived class is called. So in essence, the overloaded \u003ccode\u003eoperator delete\u003c/code\u003e 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 \u003ccode\u003eoperator delete\u003c/code\u003e will be called.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eIMPLEMENT_ALLOCATOR\u003c/code\u003e macro is the source file portion of the interface and should be placed in file scope.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nIMPLEMENT_ALLOCATOR(MyClass, 0, 0)\u003c/pre\u003e\n\n\u003cp\u003eOnce the macros are in place, the caller can create and destroy instances of this class and deleted object stored will be recycled:\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nMyClass* myClass = new MyClass();\ndelete myClass;\u003c/pre\u003e\n\n\u003cp\u003eBoth single and multiple inheritance situations work with the \u003ccode\u003eAllocator\u003c/code\u003e class. For example, assuming the class \u003ccode\u003eDerived\u003c/code\u003e inherits from class \u003ccode\u003eBase\u003c/code\u003e the following code fragment is legal.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nBase* base = new Derived;\ndelete base;\u003c/pre\u003e\n\n# Run Time\n\n\u003cp\u003eAt run time, \u003ccode\u003eAllocator\u003c/code\u003e will initially have no blocks within the free-list, so the first call to \u003ccode\u003eAllocate()\u003c/code\u003e 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.\u003c/p\u003e\n\n\u003cp\u003eCompared 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.\u003c/p\u003e\n\n# Benchmarking\n\n\u003cp\u003eBenchmarking the \u003ccode\u003eAllocator\u003c/code\u003e 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\u0026nbsp;tests the speed improvement. See the attached source code for the exact algorithm.\u0026nbsp;\u003c/p\u003e\n\n\u003ch4\u003eWindows Allocation\u0026nbsp;Times in Milliseconds\u003c/h4\u003e\n\n\u003ctable class=\"ArticleTable\"\u003e\n\t\u003cthead\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eMode\u003c/td\u003e\n\t\t\t\u003ctd\u003eRun\u003c/td\u003e\n\t\t\t\u003ctd\u003eBenchmark Time (mS)\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eDebug Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e1640\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eDebug Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e1864\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eDebug Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e1855\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e55\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e47\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e47\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e19\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e7\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e7\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e30\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e7\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e7\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cp\u003eWindows 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\u0026nbsp;Visual Studio by setting\u0026nbsp;\u003cstrong\u003e_NO_DEBUG_HEAP=1\u003c/strong\u003e in the \u003cstrong\u003eDebugging \u0026gt; Environment\u0026nbsp;\u003c/strong\u003eproject option.\u0026nbsp;\u003c/p\u003e\n\n\u003cp\u003eThe debug global heap is predictably the slowest at about 1.8 seconds. The release heap is much faster at ~50mS. This benchmark\u0026nbsp;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\u0026#39;s implementation.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eAllocator\u003c/code\u003e running in static pool mode doesn\u0026#39;t rely upon the heap. This has a fast\u0026nbsp;execution time of around\u0026nbsp;7mS once the free-list is populated with blocks. The 19mS on Run 1\u0026nbsp;accounts for dicing up the fixed memory pool into individual blocks on the first run.\u0026nbsp;\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eAllocator\u003c/code\u003e 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.\u0026nbsp;\u003c/p\u003e\n\n\u003cp\u003eAs the benchmarking shows, the \u003ccode\u003eAllocator\u003c/code\u003e is highly efficient and about seven\u0026nbsp;times faster than the Windows\u0026nbsp;global release heap.\u003c/p\u003e\n\n\u003cp\u003eFor comparison on an embedded system, I ran the same tests using Keil running on\u0026nbsp;an ARM STM32F4 CPU at\u0026nbsp;168MHz. I had to lower the maximum blocks to 500 and the blocks sizes to 32 and\u0026nbsp;16 bytes due to the constrained resources. Here are the results.\u003c/p\u003e\n\n\u003ch4\u003eARM STM32F4 Allocation\u0026nbsp;Times in Milliseconds\u003c/h4\u003e\n\n\u003ctable class=\"ArticleTable\"\u003e\n\t\u003cthead\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eMode\u003c/td\u003e\n\t\t\t\u003ctd\u003eRun\u003c/td\u003e\n\t\t\t\u003ctd\u003eBenchmark Time (mS)\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e11.6\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease\u0026nbsp;\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e11.6\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eGlobal Heap\u003c/td\u003e\n\t\t\t\u003ctd\u003eRelease\u0026nbsp;\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e11.6\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e0.85\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e0.79\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eStatic Pool\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e0.79\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e1\u003c/td\u003e\n\t\t\t\u003ctd\u003e1.19\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e2\u003c/td\u003e\n\t\t\t\u003ctd\u003e0.79\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\t\u003ctr\u003e\n\t\t\t\u003ctd\u003eAllocator\u003c/td\u003e\n\t\t\t\u003ctd\u003eHeap Blocks\u003c/td\u003e\n\t\t\t\u003ctd\u003e3\u003c/td\u003e\n\t\t\t\u003ctd\u003e0.79\u003c/td\u003e\n\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cp\u003eAs the ARM benchmark results show, the \u003ccode\u003eAllocator\u003c/code\u003e class is about 15 times faster which is quite significant. It should be noted that the benchmark test really aggravated\u0026nbsp;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.\u003c/p\u003e\n\n# Allocator Decisions\n\n\u003cp\u003eThe first decision to make is do you need an allocator at all. If you don\u0026#39;t have an execution speed or fault-tolerance requirement for your project, you probably don\u0026#39;t need a custom allocator and the global heap will work just fine.\u003c/p\u003e\n\n\u003cp\u003eOn 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 \u003ccode\u003estatic\u003c/code\u003e pool method to create statically allocated pools thus eliminating all global heap access. A few compile-time macros switch between the modes.\u003c/p\u003e\n\n\u003cp\u003eAlternatively, 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.\u003c/p\u003e\n\n\u003cp\u003eWhile not implemented in the source code due to multi-threaded issues outside the scope of this article, it is easy have the \u003ccode\u003eAllocator\u003c/code\u003e constructor keep a \u003ccode\u003estatic\u003c/code\u003e 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 \u003ccode\u003eGetBlockCount()\u003c/code\u003e and \u003ccode\u003eGetName()\u003c/code\u003e 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.\u003c/p\u003e\n\n# Debugging Memory Leaks\n\n\u003cp\u003eDebugging 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 \u003ccode\u003eAllocator\u003c/code\u003e, memory leaks are a bit easier to find since the \u003ccode\u003eAllocator\u003c/code\u003e tracks the total block count. Repeatedly output (to the console for example) the \u003ccode\u003eGetBlockCount()\u003c/code\u003e and \u003ccode\u003eGetName()\u003c/code\u003e for each allocator instance and comparing the differences should expose the allocator with an ever increasing block count.\u003c/p\u003e\n\n# Error Handling\n\n\u003cp\u003eAllocation 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\u0026#39;s error-handling function is called via the new-handler function pointer. By assigning the user\u0026#39;s function address to the new-handler, the memory manager is able to call a custom error-handling routine. To make the \u003ccode\u003eAllocator\u003c/code\u003e class\u0026#39;s error handling consistent, allocations that exceed the pool\u0026#39;s storage capacity also call the function pointed to by new-handler, centralizing all memory allocation faults in one place.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nstatic void out_of_memory()\n{\n    // new-handler function called by Allocator when pool is out of memory\n    assert(0);\n}\n\nint _tmain(int argc, _TCHAR* argv[])\n{\n    std::set_new_handler(out_of_memory);\n...\u003c/pre\u003e\n\n# Limitations\n\n\u003cp\u003eThe class does not support arrays of objects. An overloaded \u003ccode\u003eoperator new[]\u003c/code\u003e poses a problem for the object recycling method. When this function is called, the \u003ccode\u003esize_t\u003c/code\u003e 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\u0026#39;t guarantee that the blocks are within a contiguous space, which an array needs. Since \u003ccode\u003eAllocator\u003c/code\u003e only handles same size blocks, arrays are not allowed.\u003c/p\u003e\n\n# Porting Issues\n\n\u003cp\u003e\u003ccode\u003eAllocator\u003c/code\u003e calls the \u003ccode\u003enew_handler()\u003c/code\u003e directly when the \u003ccode\u003estatic\u003c/code\u003e 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.\u003c/p\u003e\n\n# Reference Articles\n\n\u003cp\u003e\u003cb\u003e\u003ca href=\"https://github.com/endurodave/xallocator\"\u003eReplace malloc/free with a Fast Fixed Block Memory Allocator\u003c/a\u003e\u003c/b\u003e\u0026nbsp; - use \u003ccode\u003eAllocator\u003c/code\u003e to create a really fast \u003ccode\u003emalloc()\u003c/code\u003e and \u003ccode\u003efree()\u003c/code\u003e CRT replacement.\u003c/p\u003e\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fallocator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendurodave%2Fallocator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fallocator/lists"}