├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include └── boost_intrusive_pool.hpp ├── renovate.json └── tests ├── bench_plot_results.py ├── json-lib.cpp ├── json-lib.h ├── performance_tests.cpp ├── performance_timing.h ├── results ├── bench_results_gnulibc.json ├── bench_results_jemalloc.json ├── bench_results_tcmalloc.json ├── pattern_1_gnulibc.png ├── pattern_1_jemalloc.png ├── pattern_1_tcmalloc.png ├── pattern_2_gnulibc.png ├── pattern_2_jemalloc.png └── pattern_2_tcmalloc.png ├── tracing_malloc.h ├── tutorial.cpp └── unit_tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | ColumnLimit: '120' 4 | ... 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | run_unit_test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # install deps 15 | - uses: actions/checkout@v4 16 | - name: install debian-packaged dependencies 17 | run: sudo apt install -y libboost-dev valgrind 18 | 19 | # test 20 | - name: run unit tests 21 | run: make test 22 | - name: run unit tests with VALGRIND 23 | run: make test_valgrind 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | .pydevproject 4 | .settings/ 5 | tests/*.o 6 | tests/performance_tests 7 | tests/tutorial 8 | tests/unit_tests 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Francesco Montorsi 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Very simple quick-and-dirty makefile to build tutorial program and unit tests 2 | # Assumes Boost and GCC are available in standard paths. 3 | 4 | CC=g++ 5 | CXXFLAGS_OPT= -fPIC -std=c++11 -Iinclude -O3 -pthread 6 | CXXFLAGS_DBG= -fPIC -std=c++11 -Iinclude -g -O0 -pthread 7 | DEBUGFLAGS= -DBOOST_INTRUSIVE_POOL_DEBUG_CHECKS=1 -DBOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS=1 8 | 9 | DEPS = \ 10 | include/boost_intrusive_pool.hpp \ 11 | tests/tracing_malloc.h 12 | BINS = \ 13 | tests/tutorial \ 14 | tests/unit_tests \ 15 | tests/performance_tests 16 | 17 | 18 | # Constants for performance tests: 19 | 20 | # tested on Ubuntu 18.04 21 | # apt install libtcmalloc-minimal4 libjemalloc1 22 | LIBTCMALLOC_LOCATION := /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 23 | ifeq (,$(wildcard $(LIBTCMALLOC_LOCATION))) 24 | $(info Could not find libtcmalloc in Ubuntu default location, trying RHEL default location) 25 | LIBTCMALLOC_LOCATION := /usr/lib64/libtcmalloc_minimal.so.4.4.5 26 | ifeq (,$(wildcard $(LIBTCMALLOC_LOCATION))) 27 | $(info Could not find libtcmalloc... benchmarks using that will not run) 28 | endif 29 | endif 30 | 31 | LIBJEMALLOC_LOCATION := /usr/lib/x86_64-linux-gnu/libjemalloc.so.1 32 | ifeq (,$(wildcard $(LIBJEMALLOC_LOCATION))) 33 | $(info Could not find libjemalloc in Ubuntu default location, trying RHEL default location) 34 | LIBJEMALLOC_LOCATION := /usr/lib64/libjemalloc.so.1 35 | ifeq (,$(wildcard $(LIBJEMALLOC_LOCATION))) 36 | $(info Could not find libjemalloc... benchmarks using that will not run) 37 | endif 38 | endif 39 | 40 | VALGRIND_LOGFILE_POSTFIX:=unit-tests-$(shell date +%F-%H%M%S) 41 | VALGRIND_SUPP:=valgrind.supp 42 | VALGRIND_COMMON_OPTS:=--gen-suppressions=all --time-stamp=yes --error-limit=no 43 | # --suppressions=$(VALGRIND_SUPP) 44 | MEMCHECK_COMMON_OPTS:=--tool=memcheck $(VALGRIND_COMMON_OPTS) --track-origins=yes --malloc-fill=AF --free-fill=BF --leak-check=full 45 | 46 | # Targets 47 | 48 | all: $(BINS) 49 | @echo "Run tests/tutorial for a short tutorial (read comments in the source code!)" 50 | 51 | format_check: 52 | # if you have clang-format >= 10.0.0, this will work: 53 | @clang-format --dry-run --Werror include/boost_intrusive_pool.hpp 54 | 55 | test: $(BINS) 56 | tests/unit_tests --log_level=all --show_progress 57 | 58 | # just a synonim for "test": 59 | tests: test 60 | 61 | test_valgrind: 62 | valgrind $(MEMCHECK_COMMON_OPTS) --log-file=valgrind-$(VALGRIND_LOGFILE_POSTFIX).log tests/unit_tests 63 | 64 | 65 | benchmarks: 66 | @echo "Running the performance benchmarking tool without any optimized allocator:" 67 | tests/performance_tests >tests/results/bench_results_gnulibc.json 68 | @echo "Now running the performance benchmarking tool using some optimized allocator (must be installed systemwide!):" 69 | LD_PRELOAD="$(LIBTCMALLOC_LOCATION)" tests/performance_tests >tests/results/bench_results_tcmalloc.json 70 | LD_PRELOAD="$(LIBJEMALLOC_LOCATION)" tests/performance_tests >tests/results/bench_results_jemalloc.json 71 | 72 | plots: 73 | tests/bench_plot_results.py gnulibc tests/results/bench_results_gnulibc.json 74 | tests/bench_plot_results.py tcmalloc tests/results/bench_results_tcmalloc.json 75 | tests/bench_plot_results.py jemalloc tests/results/bench_results_jemalloc.json 76 | 77 | clean: 78 | rm -f $(BINS) tests/*.o 79 | 80 | .PHONY: all test tests clean 81 | 82 | 83 | # Rules 84 | 85 | %.o: %.cpp $(DEPS) 86 | $(CC) $(CXXFLAGS_DBG) $(DEBUGFLAGS) -c -o $@ $< 87 | 88 | # when compiling unit tests CPP also include DEBUGFLAGS to increase amount of checks we do: 89 | tests/unit_tests.o: tests/unit_tests.cpp 90 | $(CC) $(CXXFLAGS_DBG) $(DEBUGFLAGS) -c -o $@ $< 91 | 92 | # when compiling unit tests CPP also include DEBUGFLAGS to increase amount of checks we do: 93 | tests/performance_tests.o: tests/performance_tests.cpp 94 | $(CC) $(CXXFLAGS_OPT) -c -o $@ $< 95 | tests/json-lib.o: tests/json-lib.cpp 96 | $(CC) $(CXXFLAGS_OPT) -c -o $@ $< 97 | 98 | tests/%: tests/%.o 99 | $(CC) -o $@ $^ -pthread 100 | 101 | tests/performance_tests: tests/performance_tests.o tests/json-lib.o 102 | $(CC) -o $@ tests/performance_tests.o tests/json-lib.o $(CXXFLAGS) 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/f18m/boost-intrusive-pool.svg?branch=master)](https://travis-ci.com/f18m/boost-intrusive-pool) 2 | 3 | # Boost Intrusive Pool 4 | This project provides a C++ memory pool that is Boost-friendly and performance oriented. 5 | 6 | ## Features and Limitations 7 | The `boost_intrusive_pool` provides the following features: 8 | - **smart pointers**: once "allocated" from the pool items whose reference count goes to zero return 9 | automatically to the pool; 10 | - **zero-malloc**: after a resize of `N` items, no memory allocations are EVER done until `M<=N` active 11 | items are in use; 12 | - O(1) allocate; 13 | - O(1) destroy (pool return); 14 | - use of standard, well-defined smart pointers: `boost::intrusive_ptr<>`; see [Boost documentation](https://www.boost.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#intrusive_ptr) 15 | - polymorphic-friendly pool: if A derives from `boost_intrusive_pool_item`, and B derives from A, the 16 | memory pool of B works just fine; 17 | - Header-only; 18 | - Provides two variants: the **infinite memory pool**, which automatically enlarges if the number of active items goes over 19 | initial memory pool size, and the **bounded memory pool**, which just returns `NULL` if trying to allocate more active 20 | items than the configured limit; 21 | - **Optional** construction via an initialization function: when items are allocated out of the pool via the 22 | `boost_intrusive_pool::allocate_through_init()` API, the `init()` member function of the memory-pooled objects 23 | is called; C++11 perfect forwarding allows to pass optional parameters to the `init()` routine; 24 | - **Optional** construction via custom function: when items are allocated out of the pool via the 25 | `boost_intrusive_pool::allocate_through_function()` the provided custom function is called with the memory-pooled 26 | object as argument; 27 | - **Optional** recycling via custom function: when the pool is constructed, a custom function `std::function` can be 28 | specified; when items return to the pool it will be called with the item being recycled as parameter; this allows 29 | to perform special cleanup like releasing handles, clearing data structures, etc; 30 | - **Optional** recycling via alternative function: when items return to the pool, the memory pool can be configured 31 | to invoke the `destroy()` member function of the memory-pooled objects; this allows 32 | to perform special cleanup like releasing handles, clearing data structures, etc; 33 | 34 | Of course there are tradeoffs in the design that bring in some limitations: 35 | - requires all C++ classes stored inside the memory pool to derive from a base class `boost_intrusive_pool_item`; 36 | - provides `boost::intrusive_ptr<>` instead of the more widely-used `std::shared_ptr<>`: 37 | reason is that `std::shared_ptr<>` puts the reference count in a separate block that needs a separate allocation 38 | and thus memory pools based on `std::shared_ptr<>` (like https://github.com/steinwurf/recycle) cannot be 39 | zero-malloc due to the heap-allocated control block; 40 | - requires C++ classes stored inside the memory pool to have a default constructor: reason is that to ensure 41 | the spatial locality of allocated items (for better cache / memory performances) we use the `new[]` operator 42 | which does not allow to provide any parameter; 43 | - adds about 32 bytes of overhead to each C++ class to be stored inside the memory pool. 44 | 45 | 46 | # How to Install 47 | 48 | Since this project is header-only it does not need any specific installation, just grab the latest release and put the 49 | `boost_intrusive_pool.hpp` file in your include path. 50 | 51 | # Requirements 52 | 53 | This templated memory pool requires a C++11 compliant compiler (it has been tested with GCC 7.x and 8.x). 54 | It also requires Boost version 1.55 or higher. 55 | 56 | 57 | # A Short Tutorial 58 | 59 | The source code in [tests/tutorial.cpp](tests/tutorial.cpp) provides a short tutorial about the following topics: 60 | - `std::shared_ptr<>` 61 | - `boost::intrusive_ptr<>` 62 | - `memorypool::boost_intrusive_pool<>` (this project) 63 | 64 | and shows that: 65 | 66 | - allocating an item through `std::shared_ptr` results in the malloc of `sizeof(T)` plus about 16bytes 67 | (the control block); 68 | - allocating an item through `boost::intrusive_ptr` results in the malloc of `sizeof(T)`: the refcount 69 | is not stored in any separate control block; 70 | - creation of a `memorypool::boost_intrusive_pool<>` results in several malloc operations, but then: 71 | - creation of an item `T` from a `memorypool::boost_intrusive_pool` does not result in any malloc 72 | 73 | 74 | # Example: Using the Default Constructor 75 | 76 | In this example we show how to use memory-pooled objects that are initialized through their default constructor: 77 | 78 | ``` 79 | #include "boost_intrusive_pool.hpp" 80 | 81 | void main() 82 | { 83 | memorypool::boost_intrusive_pool pool(4); // allocate pool of size 4 84 | // this results in a big memory allocation; from now on instead it's a zero-malloc world: 85 | 86 | { 87 | // allocate without ANY call at all (max performances): 88 | boost::intrusive_ptr hdummy = pool.allocate(); 89 | 90 | // of course copying pointers around does not trigger any memory allocation: 91 | boost::intrusive_ptr hdummy2 = hdummy; 92 | 93 | // if we observed the pool now it would report: capacity=4, unused_count=3, inuse_count=1 94 | 95 | 96 | // now instead allocate using the DummyClass default ctor: 97 | boost::intrusive_ptr hdummy3 = pool.allocate_through_init(); 98 | 99 | } // this time no memory free() will happen! 100 | 101 | // if we observed the pool now it would report: capacity=4, unused_count=4, inuse_count=0 102 | } 103 | 104 | ``` 105 | 106 | 107 | # Example: Using a non-Default Constructor 108 | 109 | In this example we show how to use memory-pooled objects that are initialized through their NON default constructor: 110 | 111 | ``` 112 | #include "boost_intrusive_pool.hpp" 113 | 114 | void main() 115 | { 116 | memorypool::boost_intrusive_pool pool(4); // allocate pool of size 4 117 | // this results in a big memory allocation; from now on instead it's a zero-malloc world: 118 | 119 | { 120 | // now instead allocate using the DummyClass NON default ctor: 121 | boost::intrusive_ptr hdummy3 = pool.allocate_through_init(arg1, arg2, arg3); 122 | 123 | } // this time no memory free() will happen! 124 | } 125 | ``` 126 | 127 | # Performance Results 128 | 129 | The following tables show results of some very simple benchmarking obtained on a desktop machine: 130 | 131 | ``` 132 | Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz, 4 cores 133 | 16GB DDR3 134 | libc-2.27 (Ubuntu 18.04) 135 | ``` 136 | 137 | The memory pool implementation is compared against a "no pool" solution (the `plain_malloc` line), which simply allocates 138 | items directly from the heap through `malloc`. 139 | For both the `boost_intrusive_pool` and the `plain_malloc` a very lightweight processing is simulated on the allocated 140 | items so that these performance results show the gain you obtain if: 141 | - you intend to create a memory pool of large items, expensive to allocate each time; 142 | - the processing for each item is lightweight. 143 | 144 | The benchmarks are then repeated considering 3 different memory allocators: 145 | 1. [GNU libc](https://www.gnu.org/software/libc/) default malloc/free implementation 146 | 2. [Google perftools](https://github.com/gperftools/gperftools) also known as tcmalloc 147 | 3. [Jemalloc](http://jemalloc.net/) 148 | 149 | Moreover 2 different memory allocation/deallocation patterns are considered: 150 | 1. `Continuous allocations, bulk free at end`: all 100 thousands large objects are allocated sequentially, lightweight-processed 151 | and then released back to the pool/heap. 152 | 2. `Mixed alloc/free pattern`: the items are returned to the pool/heap in a pseudo-random order, potentially generating memory fragmentation 153 | in the `plain_malloc` implementation. 154 | 155 | Finally for each combination of memory allocator and allocation pattern we measure the mean time it takes to allocate an item 156 | (or retrieve it from the memory pool!) against the "memory pool enlarge step" configuration value. Of course the enlarge step 157 | parameter will affect only the memory pool perfomances so that in theory the "plain malloc" time should remain constant 158 | (i.e. all green lines should be flat in all graphs below!). In practice small variations are expected also in the measured 159 | times for the "plain malloc". 160 | 161 | Results for the `Continuous allocations, bulk free at end` benchmark follow: 162 | 163 | 164 | 165 | 166 | 171 | 176 | 177 | 178 | 184 | 194 | 195 | 196 | 197 |
167 | 168 | ![](tests/results/pattern_1_gnulibc.png) 169 | 170 | 172 | 173 | ![](tests/results/pattern_1_tcmalloc.png) 174 | 175 |
179 | 180 | ![](tests/results/pattern_1_jemalloc.png) 181 | 182 | 183 | 185 | 186 | Results show that with glibc allocator (regular malloc/free implementation), the use of a memory 187 | pool results in up to 44% improvement (from an average of 134ns to about 76ns). 188 | When Google's tcmalloc or jemalloc allocators are in use the improvement grows to 67% and 76% respectively. 189 | 190 | This is important to show that even if your software is using an optimized allocator the memory pool 191 | pattern will improve performances considerably. 192 | 193 |
198 | 199 | Results for the `Mixed alloc/free pattern` benchmark follow: 200 | 201 | 202 | 203 | 204 | 209 | 214 | 215 | 216 | 222 | 231 | 232 | 233 | 234 |
205 | 206 | ![](tests/results/pattern_2_gnulibc.png) 207 | 208 | 210 | 211 | ![](tests/results/pattern_2_tcmalloc.png) 212 | 213 |
217 | 218 | ![](tests/results/pattern_2_jemalloc.png) 219 | 220 | 221 | 223 | 224 | These results show that with a pattern where malloc and free operations are scattered and "randomized" 225 | a little bit, regular allocators cannot avoid memory fragmentation and less spatial locality compared 226 | to a memory pool implementation. 227 | 228 | In particular improvements go from 40% (glibc) to 53% (jemalloc) and up to 73% (tcmalloc). 229 | 230 |
235 | 236 | Of course take all these performance results with care. 237 | Actual performance gain may vary a lot depending on your rate of malloc/free operations, the pattern in which they happen, 238 | the size of the pooled items, etc etc. 239 | You can find the source code used to generate these benchmark results in the file [tests/performance_tests.cpp](tests/performance_tests.cpp). 240 | 241 | 242 | 243 | # About Thread Safety 244 | 245 | The memory pool has no locking mechanism whatsoever and is not thread safe. 246 | The reason is that this memory pool is performance oriented and locks are not that fast, specially if you have many 247 | threads continuosly allocating and releasing items to the pool. 248 | In such a scenario, the best from a performance point of view, is to simply create a memory pool for each thread. 249 | 250 | 251 | # Other Memory Pools 252 | 253 | This memory pool implementation has been inspired by a few other C++ implementations out there like: 254 | 255 | - https://github.com/steinwurf/recycle 256 | - https://thinkingeek.com/2017/11/19/simple-memory-pool/ 257 | - https://github.com/cdesjardins/QueuePtr 258 | -------------------------------------------------------------------------------- /include/boost_intrusive_pool.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A C++ memory pool that is Boost-friendly and performance oriented. 3 | * 4 | * Inspired by: 5 | * - https://thinkingeek.com/2017/11/19/simple-memory-pool/: a C++ memory pool for raw pointers 6 | * (no smart pointers) using free lists and perfect forwarding to handle non-default ctors. 7 | * - https://github.com/steinwurf/recycle: a C++ memory pool for std::shared_ptr<>, which however 8 | * produces 1 malloc operation for each allocation/recycle operation 9 | * 10 | * Author: fmontorsi 11 | * Created: Feb 2019 12 | * License: BSD license 13 | * 14 | */ 15 | 16 | #pragma once 17 | 18 | //------------------------------------------------------------------------------ 19 | // Includes 20 | //------------------------------------------------------------------------------ 21 | 22 | // #include // not really used finally 23 | #include 24 | #include 25 | 26 | //------------------------------------------------------------------------------ 27 | // Constants 28 | //------------------------------------------------------------------------------ 29 | 30 | #define BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE (64) 31 | #define BOOST_INTRUSIVE_POOL_INCREASE_STEP (64) 32 | #define BOOST_INTRUSIVE_POOL_NO_MAX_SIZE (0) 33 | 34 | #ifndef BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 35 | // if you define BOOST_INTRUSIVE_POOL_DEBUG_CHECKS=1 before including this header file, 36 | // you will activate a lot more debug checks on the memory pool to verify its integrity; 37 | // this is useful during e.g. debug builds 38 | #define BOOST_INTRUSIVE_POOL_DEBUG_CHECKS (0) 39 | #endif 40 | 41 | #ifndef BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 42 | // if you define BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS=1 before including this header file, 43 | // you will activate a pthread-specific check about correct thread access to the memory pool 44 | #define BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS (0) 45 | #else 46 | // checks are active: we use pthread_self() API: 47 | #include 48 | #endif 49 | 50 | #ifndef BOOST_INTRUSIVE_POOL_DEBUG_MAX_REFCOUNT 51 | // completely-arbitrary threshold about what range of refcounts can be considered 52 | // sane and valid and which range cannot be considered valid! 53 | #define BOOST_INTRUSIVE_POOL_DEBUG_MAX_REFCOUNT (1024) 54 | #endif 55 | 56 | //------------------------------------------------------------------------------ 57 | // Start of memorypool namespace 58 | //------------------------------------------------------------------------------ 59 | 60 | namespace memorypool { 61 | 62 | typedef enum { 63 | RECYCLE_METHOD_NONE, // when an item returns into the pool, do nothing 64 | RECYCLE_METHOD_DESTROY_FUNCTION, // when an item returns into the pool, invoke the 65 | // boost_intrusive_pool_item::destroy() virtual func 66 | RECYCLE_METHOD_CUSTOM_FUNCTION, // when an item returns into the pool, invoke a function provided at boost memory 67 | // pool init time 68 | // RECYCLE_METHOD_DTOR, 69 | } recycle_method_e; 70 | 71 | //------------------------------------------------------------------------------ 72 | // Forward declarations 73 | //------------------------------------------------------------------------------ 74 | 75 | class boost_intrusive_pool_item; 76 | 77 | //------------------------------------------------------------------------------ 78 | // boost_intrusive_pool_iface 79 | //------------------------------------------------------------------------------ 80 | 81 | class boost_intrusive_pool_iface 82 | : public boost::intrusive_ref_counter { 83 | public: 84 | virtual ~boost_intrusive_pool_iface() = default; 85 | 86 | virtual void recycle(boost_intrusive_pool_item* item) = 0; 87 | 88 | virtual bool is_bounded() const = 0; 89 | virtual bool is_memory_exhausted() const = 0; 90 | }; 91 | 92 | //------------------------------------------------------------------------------ 93 | // boost_intrusive_pool_item 94 | // Base class for any C++ class that will be used inside a boost_intrusive_pool 95 | //------------------------------------------------------------------------------ 96 | 97 | class boost_intrusive_pool_item { 98 | friend void intrusive_ptr_add_ref(boost_intrusive_pool_item* x); 99 | friend void intrusive_ptr_release(boost_intrusive_pool_item* x); 100 | 101 | public: 102 | boost_intrusive_pool_item() 103 | { 104 | m_boost_intrusive_pool_next = nullptr; 105 | m_boost_intrusive_pool_refcount = 0; 106 | m_boost_intrusive_pool_owner = nullptr; 107 | } 108 | boost_intrusive_pool_item(const boost_intrusive_pool_item& other) 109 | { 110 | // IMPORTANT: 111 | // do not copy the members of this class around: 112 | // whether "this" instance is inside a memory pool or not, that has to stay that way, 113 | // regardless of whether "other" is inside a memory pool or not. 114 | 115 | m_boost_intrusive_pool_next = nullptr; 116 | m_boost_intrusive_pool_refcount = 0; 117 | m_boost_intrusive_pool_owner = nullptr; 118 | } 119 | boost_intrusive_pool_item(const boost_intrusive_pool_item&& other) 120 | { 121 | // IMPORTANT: 122 | // do not copy the members of this class around: 123 | // whether "this" instance is inside a memory pool or not, that has to stay that way, 124 | // regardless of whether "other" is inside a memory pool or not. 125 | 126 | m_boost_intrusive_pool_next = nullptr; 127 | m_boost_intrusive_pool_refcount = 0; 128 | m_boost_intrusive_pool_owner = nullptr; 129 | } 130 | virtual ~boost_intrusive_pool_item() { } 131 | 132 | //------------------------------------------------------------------------------ 133 | // emulate the boost::intrusive_ref_counter class implementation: 134 | //------------------------------------------------------------------------------ 135 | 136 | unsigned int use_count() const noexcept { return m_boost_intrusive_pool_refcount; } 137 | 138 | boost_intrusive_pool_item& operator=(const boost_intrusive_pool_item& other) 139 | { 140 | // IMPORTANT: 141 | // do not copy the members of this class around: 142 | // whether "this" instance is inside a memory pool or not, that has to stay that way, 143 | // regardless of whether "other" is inside a memory pool or not. 144 | // This is also what boost::intrusive_ref_counter implementation does. 145 | return *this; 146 | } 147 | boost_intrusive_pool_item& operator=(const boost_intrusive_pool_item&& other) 148 | { 149 | // IMPORTANT: 150 | // do not copy the members of this class around: 151 | // whether "this" instance is inside a memory pool or not, that has to stay that way, 152 | // regardless of whether "other" is inside a memory pool or not. 153 | // This is also what boost::intrusive_ref_counter implementation does. 154 | return *this; 155 | } 156 | 157 | //------------------------------------------------------------------------------ 158 | // memorypool::boost_intrusive_pool private functions 159 | //------------------------------------------------------------------------------ 160 | 161 | boost_intrusive_pool_item* _refcounted_item_get_next() { return m_boost_intrusive_pool_next; } 162 | void _refcounted_item_set_next(boost_intrusive_pool_item* p) { m_boost_intrusive_pool_next = p; } 163 | 164 | boost::intrusive_ptr _refcounted_item_get_pool() 165 | { 166 | return m_boost_intrusive_pool_owner; 167 | } 168 | void _refcounted_item_set_pool(boost::intrusive_ptr p) 169 | { 170 | m_boost_intrusive_pool_owner = p; 171 | } 172 | 173 | //------------------------------------------------------------------------------ 174 | // default init-after-recycle, destroy-before-recycle methods: 175 | //------------------------------------------------------------------------------ 176 | 177 | virtual void destroy() { } 178 | 179 | //------------------------------------------------------------------------------ 180 | // memorypool utility functions 181 | //------------------------------------------------------------------------------ 182 | 183 | // is_in_memory_pool() returns true both in case 184 | // - this item is currently marked as "in use" in some memory pool 185 | // - this item lies unused in some memory pool 186 | // This function returns false if e.g. this item has been allocated on the heap 187 | // bypassing any memory pool mechanism. 188 | bool is_in_memory_pool() const { return m_boost_intrusive_pool_owner != nullptr; } 189 | 190 | // sanity checks for this item. Useful for debug only. 191 | void check() const 192 | { 193 | if (is_in_memory_pool()) { 194 | assert(m_boost_intrusive_pool_refcount < BOOST_INTRUSIVE_POOL_DEBUG_MAX_REFCOUNT); 195 | 196 | if (m_boost_intrusive_pool_refcount == 0) { 197 | // this item is apparently inside the free list of the memory pool: 198 | // in such case it should be always linked to the list; the only case where 199 | // the "next" pointer can be NULL is in the case the memory pool is memory-bounded 200 | // and the free items are exhausted or the memory pool is infinite but the memory is over 201 | assert(m_boost_intrusive_pool_next != nullptr || m_boost_intrusive_pool_owner->is_bounded() 202 | || m_boost_intrusive_pool_owner->is_memory_exhausted()); 203 | } else { 204 | // this item is in use and thus must be UNLINKED from the free list of the memory pool: 205 | assert(m_boost_intrusive_pool_next == nullptr); 206 | } 207 | } 208 | } 209 | 210 | private: 211 | size_t m_boost_intrusive_pool_refcount; // intrusive refcount 212 | boost_intrusive_pool_item* m_boost_intrusive_pool_next; // we use a free-list-based memory pool algorithm 213 | boost::intrusive_ptr 214 | m_boost_intrusive_pool_owner; // used for auto-return to the memory pool 215 | }; 216 | 217 | inline void intrusive_ptr_add_ref(boost_intrusive_pool_item* x) 218 | { 219 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 220 | assert(x->m_boost_intrusive_pool_refcount < BOOST_INTRUSIVE_POOL_DEBUG_MAX_REFCOUNT - 1); 221 | #endif 222 | ++x->m_boost_intrusive_pool_refcount; 223 | } 224 | 225 | inline void intrusive_ptr_release(boost_intrusive_pool_item* x) 226 | { 227 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 228 | x->check(); 229 | #endif 230 | if (--x->m_boost_intrusive_pool_refcount == 0) { 231 | if (x->m_boost_intrusive_pool_owner) 232 | x->m_boost_intrusive_pool_owner->recycle(x); 233 | else 234 | // assume the item has been allocated out of the pool: 235 | delete x; 236 | } 237 | } 238 | 239 | //------------------------------------------------------------------------------ 240 | // boost_intrusive_pool_arena 241 | // Internal helper class for a boost_intrusive_pool. 242 | //------------------------------------------------------------------------------ 243 | 244 | // Arena of items. This is just an array of items and a pointer 245 | // to another arena. All arenas are singly linked between them. 246 | template class boost_intrusive_pool_arena { 247 | public: 248 | // Creates an arena with arena_size items. 249 | boost_intrusive_pool_arena(size_t arena_size, boost::intrusive_ptr p) 250 | { 251 | assert(arena_size > 0 && p); 252 | m_storage_size = arena_size; 253 | m_storage = new Item[arena_size]; 254 | if (m_storage) { 255 | for (size_t i = 1; i < arena_size; i++) { 256 | m_storage[i - 1]._refcounted_item_set_next(&m_storage[i]); 257 | m_storage[i - 1]._refcounted_item_set_pool(p); 258 | } 259 | m_storage[arena_size - 1]._refcounted_item_set_next(nullptr); 260 | m_storage[arena_size - 1]._refcounted_item_set_pool(p); 261 | } 262 | // else: malloc failed! 263 | 264 | m_next_arena = nullptr; 265 | } 266 | 267 | boost_intrusive_pool_arena(const boost_intrusive_pool_arena& other) = delete; 268 | boost_intrusive_pool_arena(const boost_intrusive_pool_arena&& other) = delete; 269 | 270 | ~boost_intrusive_pool_arena() 271 | { 272 | // m_storage is automatically cleaned up 273 | if (m_storage) { 274 | delete[] m_storage; 275 | m_storage = nullptr; 276 | } 277 | } 278 | 279 | // Returns a pointer to the array of items. This is used by the arena 280 | // itself. This is only used to update free_list during initialization 281 | // or when creating a new arena when the current one is full. 282 | boost_intrusive_pool_item* get_first_item() const 283 | { 284 | return dynamic_cast(&m_storage[0]); 285 | } 286 | 287 | size_t get_stored_item_count() const { return m_storage_size; } 288 | 289 | // Sets the next arena. Used when the current arena is full and 290 | // we have created this one to get more storage. 291 | void set_next_arena(boost_intrusive_pool_arena* p) 292 | { 293 | assert(!m_next_arena && p); 294 | m_next_arena = p; 295 | m_storage[m_storage_size - 1]._refcounted_item_set_next(p->get_first_item()); 296 | } 297 | boost_intrusive_pool_arena* get_next_arena() { return m_next_arena; } 298 | const boost_intrusive_pool_arena* get_next_arena() const { return m_next_arena; } 299 | 300 | boost_intrusive_pool_arena operator=(const boost_intrusive_pool_arena& other) = delete; 301 | boost_intrusive_pool_arena operator=(const boost_intrusive_pool_arena&& other) = delete; 302 | 303 | private: 304 | // Pointer to the next arena. 305 | boost_intrusive_pool_arena* m_next_arena; 306 | 307 | // Storage of this arena. 308 | // std::unique_ptr m_storage; 309 | size_t m_storage_size; 310 | Item* m_storage; 311 | }; 312 | 313 | //------------------------------------------------------------------------------ 314 | // boost_intrusive_pool 315 | // The actual memory pool implementation. 316 | //------------------------------------------------------------------------------ 317 | 318 | template class boost_intrusive_pool { 319 | public: 320 | // using dummy = typename std::enable_if::value>::type; 321 | 322 | // The type of pointers provided by this memory pool implementation 323 | using item_ptr = boost::intrusive_ptr; 324 | 325 | // The allocate function type 326 | using allocate_function = std::function; 327 | 328 | // The recycle function type 329 | // If specified the recycle function will be called every time a resource gets recycled into the pool. This allows 330 | // temporary resources, e.g., file handles to be closed when an object is longer used. 331 | using recycle_function = std::function; 332 | 333 | public: 334 | // Default constructor 335 | // Leaves this memory pool uninitialized. It's mandatory to invoke init() after this one. 336 | boost_intrusive_pool() { m_pool = nullptr; } 337 | 338 | // Constructs a memory pool quite small which increases its size by rather small steps. 339 | // Tuning of these steps is critical for performances. 340 | // The ctor also allows you to specify which function should be run on items returning to the pool. 341 | boost_intrusive_pool(size_t init_size, size_t enlarge_size = BOOST_INTRUSIVE_POOL_INCREASE_STEP, 342 | size_t max_size = BOOST_INTRUSIVE_POOL_NO_MAX_SIZE, recycle_method_e recycle_method = RECYCLE_METHOD_NONE, 343 | recycle_function recycle_fn = nullptr) 344 | { 345 | // NOTE: return value is ignored... if the software is out of memory... we can't do much within a ctor 346 | init(init_size, enlarge_size, max_size, recycle_method, recycle_fn); 347 | } 348 | virtual ~boost_intrusive_pool() 349 | { 350 | if (m_pool) 351 | m_pool->trigger_self_destruction(); 352 | } 353 | 354 | // Copy constructor 355 | boost_intrusive_pool(const boost_intrusive_pool& other) = delete; 356 | 357 | // Move constructor 358 | boost_intrusive_pool(boost_intrusive_pool&& other) = delete; 359 | 360 | // Copy assignment 361 | boost_intrusive_pool& operator=(const boost_intrusive_pool& other) = delete; 362 | 363 | // Move assignment 364 | boost_intrusive_pool& operator=(boost_intrusive_pool&& other) = delete; 365 | 366 | //------------------------------------------------------------------------------ 367 | // configuration/initialization methods 368 | //------------------------------------------------------------------------------ 369 | 370 | bool init(size_t init_size = BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE, 371 | size_t enlarge_size = BOOST_INTRUSIVE_POOL_INCREASE_STEP, size_t max_size = BOOST_INTRUSIVE_POOL_NO_MAX_SIZE, 372 | recycle_method_e recycle_method = RECYCLE_METHOD_NONE, recycle_function recycle_fn = nullptr) 373 | { 374 | assert(m_pool == nullptr); // cannot initialize twice the memory pool 375 | assert(init_size > 0); 376 | assert((max_size == BOOST_INTRUSIVE_POOL_NO_MAX_SIZE) || (max_size >= init_size && enlarge_size > 0)); 377 | 378 | m_pool = boost::intrusive_ptr(new impl(enlarge_size, max_size, recycle_method, recycle_fn)); 379 | 380 | // do initial malloc 381 | return m_pool->enlarge(init_size); 382 | } 383 | 384 | void set_recycle_method(recycle_method_e method, recycle_function recycle_fn = nullptr) 385 | { 386 | assert(m_pool); // pool must be initialized 387 | m_pool->set_recycle_method(method, recycle_fn); 388 | } 389 | 390 | //------------------------------------------------------------------------------ 391 | // allocate method variants 392 | //------------------------------------------------------------------------------ 393 | 394 | // Returns the first available free item. 395 | item_ptr allocate() 396 | { 397 | assert(m_pool); // pool must be initialized 398 | Item* recycled_item = m_pool->allocate_safe_get_recycled_item(); 399 | if (!recycled_item) 400 | return nullptr; 401 | 402 | // in this case we don't need to call ANY function 403 | 404 | item_ptr ret_ptr(recycled_item); 405 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 406 | ret_ptr->check(); 407 | #endif 408 | return ret_ptr; 409 | } 410 | 411 | // Returns first available free item or, if necessary and the memory pool is unbounded and has not reached the 412 | // maximum size, allocates a new item. Uses C++11 perfect forwarding to the init() function of the memory pooled 413 | // item. 414 | template item_ptr allocate_through_init(Args&&... args) 415 | { 416 | assert(m_pool); // pool must be initialized 417 | Item* recycled_item = m_pool->allocate_safe_get_recycled_item(); 418 | if (!recycled_item) 419 | return nullptr; 420 | 421 | // Construct the object in the obtained storage 422 | // using the init() method of the item itself: 423 | recycled_item->init(std::forward(args)...); 424 | 425 | // AFTER the init() call, run the check() function 426 | item_ptr ret_ptr(recycled_item); 427 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 428 | ret_ptr->check(); 429 | #endif 430 | return ret_ptr; 431 | } 432 | 433 | // Returns first available free item or, if necessary and the memory pool is unbounded, 434 | // allocates a new item. 435 | template item_ptr allocate_through_function(allocate_function fn) 436 | { 437 | assert(m_pool); // pool must be initialized 438 | Item* recycled_item = m_pool->allocate_safe_get_recycled_item(); 439 | if (!recycled_item) 440 | return nullptr; 441 | 442 | // Construct the object in the obtained storage 443 | // uses perfect forwarding to the class ctor: 444 | /// new (recycled_item) Item(std::forward(args)...); 445 | fn(*recycled_item); 446 | 447 | // relinking the item to the pool is instead a critical step: we just executed 448 | // the ctor of the recycled item; that resulted in a call to 449 | // boost_intrusive_pool_item::boost_intrusive_pool_item()! 450 | recycled_item->_refcounted_item_set_pool(m_pool); 451 | 452 | // AFTER the ctor call, run the check() function 453 | item_ptr ret_ptr(recycled_item); 454 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 455 | ret_ptr->check(); 456 | #endif 457 | return ret_ptr; 458 | } 459 | 460 | //------------------------------------------------------------------------------ 461 | // other functions operating on items 462 | //------------------------------------------------------------------------------ 463 | 464 | void clear() 465 | { 466 | if (!m_pool) 467 | return; // nothing to do 468 | 469 | // VERY IMPORTANT: this function is very tricky: to do this correctly we cannot 470 | // simply call m_pool->clear(): that would remove all arenas that are the memory 471 | // support of memory pool items. 472 | // At this point we don't know yet if there are boost::intrusive_ptr<> out there 473 | // still alive... so we must play safe: 474 | size_t init_size = m_pool->m_enlarge_step; 475 | size_t enlarge_size = m_pool->m_enlarge_step; 476 | size_t max_size = m_pool->m_max_size; 477 | recycle_method_e method = m_pool->m_recycle_method; 478 | recycle_function recycle = m_pool->m_recycle_fn; 479 | m_pool->trigger_self_destruction(); 480 | m_pool = nullptr; // release old pool 481 | m_pool = boost::intrusive_ptr(new impl(enlarge_size, max_size, method, recycle)); 482 | } 483 | 484 | void check() 485 | { 486 | if (!m_pool) 487 | return; // nothing to do 488 | m_pool->check(); 489 | } 490 | 491 | //------------------------------------------------------------------------------ 492 | // getters 493 | //------------------------------------------------------------------------------ 494 | 495 | // returns true if there are no elements in use. 496 | // Note that if empty()==true, it does not mean that capacity()==0 as well! 497 | bool empty() const { return m_pool ? m_pool->empty() : true; } 498 | 499 | bool is_bounded() const { return m_pool ? m_pool->is_bounded() : false; } 500 | 501 | bool is_limited() const { return m_pool ? m_pool->is_limited() : false; } 502 | 503 | bool is_memory_exhausted() const { return m_pool ? m_pool->is_memory_exhausted() : false; } 504 | 505 | // returns the current (=maximum) capacity of the object pool 506 | size_t capacity() const { return m_pool ? m_pool->capacity() : 0; } 507 | 508 | size_t max_size() const { return m_pool ? m_pool->max_size() : 0; } 509 | 510 | // returns the number of free entries of the pool 511 | size_t unused_count() const { return m_pool ? m_pool->unused_count() : 0; } 512 | 513 | // returns the number of items currently malloc()ed from this pool 514 | size_t inuse_count() const { return m_pool ? m_pool->inuse_count() : 0; } 515 | 516 | // returns the number of mallocs done so far 517 | size_t enlarge_steps_done() const { return m_pool ? m_pool->enlarge_steps_done() : 0; } 518 | 519 | private: 520 | /// The actual pool implementation. We use the 521 | /// enable_shared_from_this helper to make sure we can pass a 522 | /// "back-pointer" to the pooled objects. The idea behind this 523 | /// is that we need objects to be able to add themselves back 524 | /// into the pool once they go out of scope. 525 | class impl : public boost_intrusive_pool_iface { 526 | public: 527 | impl(size_t enlarge_size, size_t max_size, recycle_method_e method, recycle_function recycle) 528 | { 529 | // assert(enlarge_size > 0); // NOTE: enlarge_size can be zero to create a limited-size memory pool 530 | 531 | // configurations 532 | m_recycle_method = method; 533 | m_recycle_fn = recycle; 534 | m_enlarge_step = enlarge_size; 535 | m_max_size = max_size; 536 | 537 | // status 538 | m_first_free_item = nullptr; 539 | m_first_arena = nullptr; 540 | m_last_arena = nullptr; 541 | m_memory_exhausted = false; 542 | m_trigger_self_destruction = false; 543 | 544 | // stats 545 | m_free_count = 0; 546 | m_inuse_count = 0; 547 | m_total_count = 0; 548 | 549 | #if BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 550 | m_allowed_thread = 0; 551 | #endif 552 | } 553 | 554 | ~impl() 555 | { 556 | // if this dtor is called, it means that all memory pooled items have been destroyed: 557 | // they are holding a shared_ptr<> back to us, so if one of them was alive, this dtor would not be called! 558 | clear(); 559 | } 560 | 561 | void set_recycle_method(recycle_method_e method, recycle_function recycle = nullptr) 562 | { 563 | m_recycle_method = method; 564 | m_recycle_fn = recycle; 565 | } 566 | 567 | void trigger_self_destruction() 568 | { 569 | m_trigger_self_destruction = true; 570 | 571 | // walk over the free list and reduce our own refcount by removing the link between the items and ourselves: 572 | // this is important because it allows the last item that will return to this pool to trigger the pool 573 | // dtor: see recycle() implementation 574 | boost_intrusive_pool_item* pcurr = m_first_free_item; 575 | while (pcurr) { 576 | pcurr->_refcounted_item_set_pool(nullptr); 577 | pcurr = pcurr->_refcounted_item_get_next(); 578 | } 579 | } 580 | 581 | size_t get_effective_enlarge_step() const 582 | { 583 | size_t enlarge_step = m_enlarge_step; 584 | if (m_enlarge_step > 0 && (m_max_size > 0 && (m_total_count + m_enlarge_step > m_max_size))) 585 | enlarge_step = m_max_size - m_total_count; // enlarge_step can be zero if we reach the max_size 586 | return enlarge_step; 587 | } 588 | 589 | Item* allocate_safe_get_recycled_item() 590 | { 591 | #if BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 592 | if (m_allowed_thread == 0) 593 | m_allowed_thread = pthread_self(); 594 | else 595 | assert(m_allowed_thread == pthread_self()); 596 | #endif 597 | 598 | if (m_free_count == 0) { 599 | assert(m_first_free_item == nullptr); 600 | size_t enlarge_step = get_effective_enlarge_step(); 601 | if (enlarge_step == 0 || !enlarge(enlarge_step)) { 602 | m_memory_exhausted = true; 603 | return nullptr; // allocation by enlarge() failed or this is a fixed-size memory pool! 604 | } 605 | } 606 | 607 | // get first item from free list 608 | assert(m_first_free_item != nullptr); 609 | Item* recycled_item = dynamic_cast(m_first_free_item); // downcast (base class -> derived class) 610 | assert(recycled_item != nullptr); // we always allocate all items of the same type, 611 | // so the dynamic cast cannot fail 612 | assert( 613 | recycled_item->_refcounted_item_get_pool().get() == this); // this was set during arena initialization 614 | // and must be valid at all times 615 | 616 | // update stats 617 | m_free_count--; 618 | m_inuse_count++; 619 | 620 | // update the pointer to the next free item available 621 | m_first_free_item = m_first_free_item->_refcounted_item_get_next(); 622 | if (m_first_free_item == nullptr && m_enlarge_step > 0) { 623 | size_t enlarge_step = get_effective_enlarge_step(); 624 | if (enlarge_step == 0) { // enlarge_step can be zero if we reach the max_size 625 | m_memory_exhausted = true; 626 | } else { 627 | // this is a memory pool which can be still enlarged: 628 | // exit the function leaving the m_first_free_item as a valid pointer to a free item! 629 | // this is just to simplify debugging and make more effective the check() function implementation! 630 | 631 | assert(m_free_count == 0); 632 | if (!enlarge(enlarge_step)) { 633 | m_memory_exhausted = true; 634 | // We tried to fetch memory from the O.S. but we failed. However we succeeded in getting the 635 | // last available item. So fallback and provide that last item to the caller. 636 | } 637 | } 638 | } 639 | 640 | // unlink the item to return 641 | recycled_item->_refcounted_item_set_next(nullptr); 642 | return recycled_item; 643 | } 644 | 645 | bool enlarge(size_t arena_size) 646 | { 647 | #if BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 648 | assert(m_allowed_thread == 0 || m_allowed_thread == pthread_self()); 649 | #endif 650 | // If the current arena is full, create a new one. 651 | boost_intrusive_pool_arena* new_arena = new boost_intrusive_pool_arena(arena_size, this); 652 | if (!new_arena) 653 | return false; // malloc failed... memory finished... very likely this is a game over 654 | 655 | // Link the new arena to the last one. 656 | if (m_last_arena) 657 | m_last_arena->set_next_arena(new_arena); 658 | if (m_first_arena == nullptr) 659 | m_first_arena = new_arena; // apparently we are initializing the memory pool for the very first time 660 | 661 | // Seek pointer to last arena 662 | m_last_arena = new_arena; 663 | 664 | // Update the free_list with the storage of the just created arena. 665 | if (m_first_free_item == nullptr) 666 | m_first_free_item = m_last_arena->get_first_item(); 667 | 668 | m_free_count += arena_size; 669 | m_total_count += arena_size; 670 | 671 | return true; 672 | } 673 | 674 | virtual void recycle(boost_intrusive_pool_item* pitem_base) override 675 | { 676 | #if BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 677 | assert(m_allowed_thread != 0); 678 | assert(m_allowed_thread == pthread_self()); 679 | #endif 680 | assert(pitem_base 681 | && pitem_base->_refcounted_item_get_next() 682 | == nullptr); // Recycling an item that has been already recycled? 683 | assert(pitem_base->_refcounted_item_get_pool().get() == this); 684 | 685 | Item* pitem = dynamic_cast(pitem_base); // downcast (base class -> derived class) 686 | assert(pitem != nullptr); // we always allocate all items of the same type, 687 | // so the dynamic cast cannot fail 688 | switch (m_recycle_method) { 689 | case RECYCLE_METHOD_NONE: 690 | break; 691 | 692 | case RECYCLE_METHOD_DESTROY_FUNCTION: 693 | pitem->destroy(); 694 | break; 695 | 696 | case RECYCLE_METHOD_CUSTOM_FUNCTION: 697 | m_recycle_fn(*pitem); 698 | break; 699 | 700 | // the big problem with using the class destructor is that the virtual table of the item will 701 | // be destroyed; attempting to dynamic_cast<> the item later will fail (NULL returned). 702 | // so this recycling option is disabled for now 703 | // case RECYCLE_METHOD_DTOR: 704 | // Destroy the object using its dtor: 705 | // pitem->Item::~Item(); 706 | // break; 707 | } 708 | 709 | // sanity check: 710 | if (!is_bounded()) { 711 | assert(m_first_free_item != nullptr || m_memory_exhausted); 712 | } 713 | 714 | // Add the item at the beginning of the free list. 715 | pitem_base->_refcounted_item_set_next(m_first_free_item); 716 | m_first_free_item = pitem_base; 717 | m_free_count++; 718 | 719 | assert(m_inuse_count > 0); 720 | m_inuse_count--; 721 | 722 | #if BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 723 | pitem_base->check(); 724 | #endif 725 | 726 | // test for self-destruction: 727 | // is this an orphan pool (i.e. a pool without any boost_intrusive_pool<> associated to it anymore)? 728 | if (m_trigger_self_destruction) { 729 | // in such case break the link between the items being recycled and this pool: 730 | // in this way the very last memory-pooled item returning to this pool will lower the refcount 731 | // to this pool to zero, and impl::~impl() will get called, freeing all arenas! 732 | pitem->_refcounted_item_set_pool(nullptr); 733 | } 734 | } 735 | 736 | //------------------------------------------------------------------------------ 737 | // other functions operating on items 738 | //------------------------------------------------------------------------------ 739 | 740 | // THIS IS A SUPER DANGEROUS FUNCTION: IT JUST REMOVES ALL ARENAS OF THIS MEMORY POOL WITHOUT ANY 741 | // CHECK WHETHER THERE ARE boost::intrusive_ptr<> OUT THERE STILL POINTING TO ITEMS INSIDE THOSE 742 | // ARENAS. USE 743 | void clear() 744 | { 745 | if (m_first_arena) { 746 | size_t init_size = m_first_arena->get_stored_item_count(); 747 | boost_intrusive_pool_arena* pcurr = m_first_arena; 748 | while (pcurr) { 749 | boost_intrusive_pool_arena* pnext = pcurr->get_next_arena(); 750 | delete pcurr; 751 | pcurr = pnext; 752 | } 753 | } else { 754 | // this memory pool has just been clear()ed... the last arena pointer should be null as well: 755 | assert(m_last_arena == nullptr); 756 | } 757 | 758 | // status 759 | m_first_free_item = nullptr; 760 | m_first_arena = nullptr; 761 | m_last_arena = nullptr; 762 | m_memory_exhausted = false; 763 | 764 | // stats 765 | m_free_count = 0; 766 | m_inuse_count = 0; 767 | m_total_count = 0; 768 | } 769 | 770 | void check() 771 | { 772 | if (m_first_arena) { 773 | // this memory pool has been correctly initialized 774 | assert(m_last_arena); 775 | assert(m_total_count > 0); 776 | 777 | // this condition should hold at any time: 778 | assert(m_free_count + m_inuse_count == m_total_count); 779 | if (is_bounded()) { 780 | // when the memory pool is bounded it contains only 1 arena of a fixed size: 781 | assert(m_first_arena == m_last_arena); 782 | } else { 783 | // infinite or max size memory pool: either we have a valid free element or the last malloc() must 784 | // have failed or the maximum size has been reached: 785 | assert(m_first_free_item != nullptr || m_memory_exhausted); 786 | } 787 | } else { 788 | // this memory pool has just been cleared with clear() apparently: 789 | assert(!m_last_arena); 790 | assert(!m_first_free_item); 791 | assert(m_free_count == 0); 792 | assert(m_inuse_count == 0); 793 | assert(m_total_count == 0); 794 | } 795 | } 796 | 797 | //------------------------------------------------------------------------------ 798 | // getters 799 | //------------------------------------------------------------------------------ 800 | 801 | // returns true if there are no elements in use. 802 | // Note that if empty()==true, it does not mean that capacity()==0 as well! 803 | bool empty() const { return m_free_count == m_total_count; } 804 | 805 | bool is_bounded() const { return m_enlarge_step == 0; } 806 | 807 | bool is_limited() const { return (m_enlarge_step == 0 || m_max_size != 0); } 808 | 809 | bool can_be_enlarged() const { return m_enlarge_step > 0 && (m_max_size == 0 || m_total_count < m_max_size); } 810 | 811 | bool is_memory_exhausted() const { return m_memory_exhausted; } 812 | 813 | // returns the current (=maximum) capacity of the object pool 814 | size_t capacity() const { return m_total_count; } 815 | 816 | size_t max_size() const { return (m_enlarge_step != 0) ? m_max_size : m_total_count; } 817 | 818 | // returns the number of free entries of the pool 819 | size_t unused_count() const { return m_free_count; } 820 | 821 | // returns the number of items currently malloc()ed from this pool 822 | size_t inuse_count() const { return m_inuse_count; } 823 | 824 | // returns the number of mallocs done so far 825 | size_t enlarge_steps_done() const 826 | { 827 | size_t num_arenas_allocated = 0; 828 | 829 | const boost_intrusive_pool_arena* pcurr = m_first_arena; 830 | while (pcurr) { 831 | pcurr = pcurr->get_next_arena(); 832 | num_arenas_allocated++; 833 | } 834 | 835 | return num_arenas_allocated; 836 | } 837 | 838 | public: 839 | // The recycle strategy & function 840 | recycle_method_e m_recycle_method; 841 | recycle_function m_recycle_fn; 842 | 843 | // How many new items to add each time the pool becomes full? 844 | // If this is zero, then this is a bounded pool, which cannot grow beyond 845 | // the initial size provided at construction time. 846 | size_t m_enlarge_step; 847 | // Maximum pool size. 848 | // If this is zero, then it is ignored. 849 | // If this is greater then zero, then no more items will be added to the pull if resulting size would exceed 850 | // this value. If enlarge_step is zero, the max_size parameter become meaningless. 851 | size_t m_max_size; 852 | 853 | // Pointers to first and last arenas. 854 | // First arena is changed only at 855 | // - construction time 856 | // - in clear() 857 | // - in reset(size_t) 858 | // Pointer to last arena is instead updated on every enlarge step. 859 | boost_intrusive_pool_arena* m_first_arena; 860 | boost_intrusive_pool_arena* m_last_arena; 861 | 862 | // List of free elements. The list can be threaded between different arenas 863 | // depending on the deallocation pattern. 864 | // This pointer can be NULL only whether: 865 | // - an infinite memory pool has exhausted memory (malloc returned NULL); 866 | // - a bounded memory pool has exhausted all its items 867 | // - a maximum size memory pool has exhausted all its items and reached the limit 868 | // In such cases m_memory_exhausted==true 869 | boost_intrusive_pool_item* m_first_free_item; 870 | // This flag can be true if allocation by enlarge() failed or this is a fixed-size memory pool or this is a 871 | // maximum size memory pool! 872 | bool m_memory_exhausted; 873 | 874 | // stats 875 | // This should hold always: 876 | // m_free_count+m_inuse_count == m_total_count 877 | size_t m_free_count; 878 | size_t m_inuse_count; 879 | size_t m_total_count; 880 | 881 | bool m_trigger_self_destruction; 882 | 883 | #if BOOST_INTRUSIVE_POOL_DEBUG_THREAD_ACCESS 884 | pthread_t m_allowed_thread; 885 | #endif 886 | }; 887 | 888 | private: 889 | // The pool impl 890 | boost::intrusive_ptr m_pool; 891 | }; 892 | 893 | } // namespace memorypool 894 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/bench_plot_results.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """Generates a figure that shows all benchmarking results 3 | """ 4 | import sys 5 | import os 6 | import json 7 | import collections 8 | 9 | import matplotlib.pyplot as plotlib 10 | 11 | BenchmarkPoint = collections.namedtuple('BenchmarkPoint', ['cpu_time', 'enlarge_step'], verbose=False) 12 | filled_markers = ('o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X') 13 | colours = ('r', 'g', 'b', 'black', 'yellow', 'purple') 14 | 15 | def plot_graphs(outfilename, benchmark_dict, title): 16 | """Plots the given dictionary of benchmark results 17 | """ 18 | plotlib.clf() 19 | plotlib.xlabel('Memory pool enlarge step') 20 | plotlib.ylabel('Average CPU time per allocation') 21 | plotlib.title(title) 22 | 23 | nmarker=0 24 | max_x=[] 25 | max_y=[] 26 | for impl_name in benchmark_dict.keys(): 27 | current_bm = benchmark_dict[impl_name] 28 | 29 | # add a line plot 30 | X = [ x.enlarge_step for x in current_bm ] 31 | Y = [ y.cpu_time for y in current_bm ] 32 | #lines = plotlib.plot(X, Y, '-' + filled_markers[nmarker], label=impl_name) 33 | #plotlib.setp(lines, 'color', colours[nmarker]) 34 | 35 | # add a semilogy plot 36 | lines = plotlib.semilogx(X, Y, '-' + filled_markers[nmarker], label=impl_name) 37 | plotlib.setp(lines, 'color', colours[nmarker]) 38 | plotlib.grid(True) 39 | 40 | # remember max X/Y 41 | max_x.append(max(X)) 42 | max_y.append(max(Y)) 43 | 44 | nmarker=nmarker+1 45 | 46 | # set some graph global props: 47 | #plotlib.xlim(0, max(max_x)*1.1) 48 | plotlib.ylim(0, max(max_y)*1.3) 49 | 50 | print("Writing plot into '%s'" % outfilename) 51 | plotlib.legend(loc='upper left') 52 | plotlib.savefig(outfilename) 53 | plotlib.show() 54 | 55 | def load_pattern(pattern): 56 | bm = {} 57 | bm['boost_intrusive_pool'] = [] 58 | bm['plain_malloc'] = [] 59 | num_items = [] 60 | 61 | num_runs = len(pattern)-1 # one entry is the pattern type 62 | print(" ...pattern %s: found %d runs" % (type, num_runs)) 63 | for run_idx in range(1,num_runs+1): 64 | run = pattern["run_" + str(run_idx)] 65 | num_items.append(run["num_items"]) 66 | 67 | # each different line must be a different key in the dictionary 68 | # each key in the dict can have multiple data points: 69 | bm['boost_intrusive_pool'].append(BenchmarkPoint(run['boost_intrusive_pool']['duration_nsec_per_item'], run['enlarge_step'])) 70 | bm['plain_malloc'].append(BenchmarkPoint(run['plain_malloc']['duration_nsec_per_item'], run['enlarge_step'])) 71 | 72 | #print(' ...found {} data points in implementation {}...'.format(len(bm[pattern_name]), pattern_name)) 73 | return [ bm, int(min(num_items)), int(max(num_items)) ] 74 | 75 | def main(args): 76 | """Program Entry Point 77 | """ 78 | if len(args) != 2: 79 | print('Usage: %s ...' % sys.argv[0]) 80 | sys.exit(os.EX_USAGE) 81 | 82 | bm = {} 83 | image_output_tag = args[0] 84 | filepath = args[1] 85 | 86 | print("Parsing '{}'...".format(filepath)) 87 | with open(filepath, 'r') as benchfile: 88 | filename = os.path.basename(filepath) 89 | 90 | try: 91 | pattern_list = json.load(benchfile) 92 | except Exception as ex: 93 | print("Invalid JSON file {}: {}".format(filepath, ex)) 94 | sys.exit(2) 95 | 96 | #print json.dumps(pattern_list, sort_keys=True, indent=4, separators=(',', ': ')) 97 | print("Found %d patterns" % len(pattern_list)) 98 | 99 | for pattern_idx in range(1,len(pattern_list)+1): 100 | pattern_name = "pattern_" + str(pattern_idx) 101 | pattern = pattern_list[pattern_name] 102 | desc = pattern["desc"] 103 | bm, min_items, max_items = load_pattern(pattern) 104 | 105 | outfilename = "tests/results/" + pattern_name + "_" + image_output_tag + ".png" 106 | title = image_output_tag + "\n" + desc + "\nNumber of items allocated: " 107 | if min_items == max_items: 108 | title += str(min_items) 109 | else: 110 | title += "[" + str(min_items) + "-" + str(max_items) + "]" 111 | 112 | plot_graphs(outfilename, bm, title) 113 | 114 | if __name__ == '__main__': 115 | main(sys.argv[1:]) 116 | 117 | 118 | -------------------------------------------------------------------------------- /tests/json-lib.cpp: -------------------------------------------------------------------------------- 1 | /* Simple library for printing JSON data. 2 | Copyright (C) 2014-2017 Free Software Foundation, Inc. 3 | This file is part of the GNU C Library. 4 | 5 | The GNU C Library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | The GNU C Library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with the GNU C Library; if not, see 17 | . */ 18 | 19 | #include "json-lib.h" 20 | #include 21 | 22 | void json_init(json_ctx_t* ctx, unsigned int indent_level, FILE* fp) 23 | { 24 | ctx->indent_level = indent_level; 25 | ctx->fp = fp; 26 | ctx->first_element = true; 27 | } 28 | 29 | static void do_indent(json_ctx_t* ctx) 30 | { 31 | char indent_buf[ctx->indent_level + 1]; 32 | 33 | memset(indent_buf, ' ', ctx->indent_level + 1); 34 | indent_buf[ctx->indent_level] = '\0'; 35 | 36 | fputs(indent_buf, ctx->fp); 37 | } 38 | 39 | void json_document_begin(json_ctx_t* ctx) 40 | { 41 | do_indent(ctx); 42 | 43 | fputs("{\n", ctx->fp); 44 | 45 | ctx->indent_level++; 46 | ctx->first_element = true; 47 | } 48 | 49 | void json_document_end(json_ctx_t* ctx) 50 | { 51 | ctx->indent_level--; 52 | 53 | do_indent(ctx); 54 | 55 | fputs("\n}", ctx->fp); 56 | } 57 | 58 | void json_attr_object_begin(json_ctx_t* ctx, const char* name) 59 | { 60 | if (!ctx->first_element) 61 | fprintf(ctx->fp, ",\n"); 62 | 63 | do_indent(ctx); 64 | 65 | fprintf(ctx->fp, "\"%s\": {\n", name); 66 | 67 | ctx->indent_level++; 68 | ctx->first_element = true; 69 | } 70 | 71 | void json_attr_object_end(json_ctx_t* ctx) 72 | { 73 | ctx->indent_level--; 74 | ctx->first_element = false; 75 | 76 | fputs("\n", ctx->fp); 77 | 78 | do_indent(ctx); 79 | 80 | fputs("}", ctx->fp); 81 | } 82 | 83 | void json_attr_string(json_ctx_t* ctx, const char* name, const char* s) 84 | { 85 | if (!ctx->first_element) 86 | fprintf(ctx->fp, ",\n"); 87 | else 88 | ctx->first_element = false; 89 | 90 | do_indent(ctx); 91 | 92 | fprintf(ctx->fp, "\"%s\": \"%s\"", name, s); 93 | } 94 | 95 | void json_attr_uint(json_ctx_t* ctx, const char* name, uint64_t d) 96 | { 97 | if (!ctx->first_element) 98 | fprintf(ctx->fp, ",\n"); 99 | else 100 | ctx->first_element = false; 101 | 102 | do_indent(ctx); 103 | 104 | fprintf(ctx->fp, "\"%s\": %" PRIu64, name, d); 105 | } 106 | 107 | void json_attr_int(json_ctx_t* ctx, const char* name, int64_t d) 108 | { 109 | if (!ctx->first_element) 110 | fprintf(ctx->fp, ",\n"); 111 | else 112 | ctx->first_element = false; 113 | 114 | do_indent(ctx); 115 | 116 | fprintf(ctx->fp, "\"%s\": %" PRId64, name, d); 117 | } 118 | 119 | void json_attr_double(json_ctx_t* ctx, const char* name, double d) 120 | { 121 | if (!ctx->first_element) 122 | fprintf(ctx->fp, ",\n"); 123 | else 124 | ctx->first_element = false; 125 | 126 | do_indent(ctx); 127 | 128 | fprintf(ctx->fp, "\"%s\": %g", name, d); 129 | } 130 | 131 | void json_array_begin(json_ctx_t* ctx, const char* name) 132 | { 133 | if (!ctx->first_element) 134 | fprintf(ctx->fp, ",\n"); 135 | 136 | do_indent(ctx); 137 | 138 | fprintf(ctx->fp, "\"%s\": [", name); 139 | 140 | ctx->indent_level++; 141 | ctx->first_element = true; 142 | } 143 | 144 | void json_array_end(json_ctx_t* ctx) 145 | { 146 | ctx->indent_level--; 147 | ctx->first_element = false; 148 | 149 | fputs("]", ctx->fp); 150 | } 151 | 152 | void json_element_string(json_ctx_t* ctx, const char* s) 153 | { 154 | if (!ctx->first_element) 155 | fprintf(ctx->fp, ", \"%s\"", s); 156 | else { 157 | fprintf(ctx->fp, "\"%s\"", s); 158 | ctx->first_element = false; 159 | } 160 | } 161 | 162 | void json_element_uint(json_ctx_t* ctx, uint64_t d) 163 | { 164 | if (!ctx->first_element) 165 | fprintf(ctx->fp, ", %" PRIu64, d); 166 | else { 167 | fprintf(ctx->fp, "%" PRIu64, d); 168 | ctx->first_element = false; 169 | } 170 | } 171 | 172 | void json_element_int(json_ctx_t* ctx, int64_t d) 173 | { 174 | if (!ctx->first_element) 175 | fprintf(ctx->fp, ", %" PRId64, d); 176 | else { 177 | fprintf(ctx->fp, "%" PRId64, d); 178 | ctx->first_element = false; 179 | } 180 | } 181 | 182 | void json_element_double(json_ctx_t* ctx, double d) 183 | { 184 | if (!ctx->first_element) 185 | fprintf(ctx->fp, ", %g", d); 186 | else { 187 | fprintf(ctx->fp, "%g", d); 188 | ctx->first_element = false; 189 | } 190 | } 191 | 192 | void json_element_object_begin(json_ctx_t* ctx) 193 | { 194 | if (!ctx->first_element) 195 | fprintf(ctx->fp, ","); 196 | 197 | fputs("\n", ctx->fp); 198 | 199 | do_indent(ctx); 200 | 201 | fputs("{\n", ctx->fp); 202 | 203 | ctx->indent_level++; 204 | ctx->first_element = true; 205 | } 206 | 207 | void json_element_object_end(json_ctx_t* ctx) 208 | { 209 | ctx->indent_level--; 210 | ctx->first_element = false; 211 | 212 | fputs("\n", ctx->fp); 213 | 214 | do_indent(ctx); 215 | 216 | fputs("}", ctx->fp); 217 | } 218 | -------------------------------------------------------------------------------- /tests/json-lib.h: -------------------------------------------------------------------------------- 1 | /* Simple library for printing JSON data. 2 | Copyright (C) 2014-2017 Free Software Foundation, Inc. 3 | This file is part of the GNU C Library. 4 | 5 | The GNU C Library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | The GNU C Library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with the GNU C Library; if not, see 17 | . */ 18 | 19 | #ifndef __JSON_LIB_H__ 20 | #define __JSON_LIB_H__ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | struct json_ctx 27 | { 28 | FILE *fp; 29 | unsigned int indent_level; 30 | bool first_element; 31 | }; 32 | 33 | typedef struct json_ctx json_ctx_t; 34 | 35 | void json_init (json_ctx_t *ctx, unsigned int indent_level, FILE *fp); 36 | void json_document_begin (json_ctx_t *ctx); 37 | void json_document_end (json_ctx_t *ctx); 38 | void json_attr_object_begin (json_ctx_t *ctx, const char *name); 39 | void json_attr_object_end (json_ctx_t *ctx); 40 | void json_attr_string (json_ctx_t *ctx, const char *name, const char *s); 41 | void json_attr_int (json_ctx_t *ctx, const char *name, int64_t d); 42 | void json_attr_uint (json_ctx_t *ctx, const char *name, uint64_t d); 43 | void json_attr_double (json_ctx_t *ctx, const char *name, double d); 44 | void json_array_begin (json_ctx_t *ctx, const char *name); 45 | void json_array_end (json_ctx_t *ctx); 46 | void json_element_string (json_ctx_t *ctx, const char *s); 47 | void json_element_int (json_ctx_t *ctx, int64_t d); 48 | void json_element_uint (json_ctx_t *ctx, uint64_t d); 49 | void json_element_double (json_ctx_t *ctx, double d); 50 | void json_element_object_begin (json_ctx_t *ctx); 51 | void json_element_object_end (json_ctx_t *ctx); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /tests/performance_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Small benchmark utility for memorypool 3 | * 4 | * Author: fmontorsi 5 | * Created: Feb 2019 6 | * License: BSD license 7 | * 8 | */ 9 | 10 | //------------------------------------------------------------------------------ 11 | // Includes 12 | //------------------------------------------------------------------------------ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "boost_intrusive_pool.hpp" 23 | #include "json-lib.h" 24 | #include "performance_timing.h" 25 | 26 | using namespace memorypool; 27 | 28 | //------------------------------------------------------------------------------ 29 | // Constants 30 | //------------------------------------------------------------------------------ 31 | 32 | #define NUM_AVERAGING_RUNS (10) 33 | 34 | typedef enum { 35 | BENCH_PATTERN_CONTINUOUS_ALLOCATION, 36 | BENCH_PATTERN_MIXED_ALLOC_FREE, 37 | } BenchPattern_t; 38 | 39 | std::string BenchPattern2String(BenchPattern_t t) 40 | { 41 | switch (t) { 42 | case BENCH_PATTERN_CONTINUOUS_ALLOCATION: 43 | return "Continuous allocations, bulk free at end"; 44 | case BENCH_PATTERN_MIXED_ALLOC_FREE: 45 | return "Mixed alloc/free pattern"; 46 | default: 47 | return ""; 48 | } 49 | } 50 | 51 | //------------------------------------------------------------------------------ 52 | // MemoryPooled items for benchmark testing: 53 | //------------------------------------------------------------------------------ 54 | 55 | class LargeObject : public memorypool::boost_intrusive_pool_item { 56 | public: 57 | LargeObject() { m_ctor_count++; } 58 | ~LargeObject() { m_dtor_count++; } 59 | 60 | virtual char dummy() const { return buf[0]; } 61 | void init(uint32_t n = 0) { buf[0] = 'a' + (n & 0x11); } 62 | 63 | static void reset_counts() 64 | { 65 | LargeObject::m_ctor_count = 0; 66 | LargeObject::m_dtor_count = 0; 67 | } 68 | 69 | char read(int idx) const { return buf[idx]; } 70 | void write(int idx, char c) { buf[idx] = c; } 71 | 72 | private: 73 | // just some fat buffer: 74 | char buf[1024]; 75 | 76 | public: 77 | static unsigned long m_ctor_count; 78 | static unsigned long m_dtor_count; 79 | }; 80 | 81 | typedef boost::intrusive_ptr HLargeObject; 82 | 83 | unsigned long LargeObject::m_ctor_count = 0; 84 | unsigned long LargeObject::m_dtor_count = 0; 85 | 86 | //------------------------------------------------------------------------------ 87 | // Reference memory pool: 88 | //------------------------------------------------------------------------------ 89 | 90 | class NoPool { 91 | public: 92 | NoPool() { } 93 | 94 | // just malloc using new() and run the default ctor: 95 | HLargeObject allocate_through_init() { return HLargeObject(new LargeObject()); } 96 | }; 97 | 98 | //------------------------------------------------------------------------------ 99 | // Benchmarks 100 | //------------------------------------------------------------------------------ 101 | 102 | template 103 | static void main_benchmark_loop( 104 | PoolUnderTest& pool, BenchPattern_t pattern, size_t num_elements, size_t& num_freed, size_t& max_active) 105 | { 106 | num_freed = 0, max_active = 0; 107 | 108 | switch (pattern) { 109 | case BENCH_PATTERN_CONTINUOUS_ALLOCATION: { 110 | std::vector helper_container; 111 | helper_container.reserve(num_elements); // this results in a single malloc that will not alter the benchmark! 112 | for (unsigned int i = 0; i < num_elements; i++) { 113 | HLargeObject myLargeObject = pool.allocate_through_init(); 114 | assert(myLargeObject); 115 | 116 | // simulate a very light processing of the allocated item: 117 | myLargeObject->write(10, myLargeObject->read(10) + 2); 118 | myLargeObject->write(20, myLargeObject->read(20) + 2); 119 | helper_container.push_back(myLargeObject); // hold a reference otherwise the item will get deallocated! 120 | 121 | max_active = std::max(max_active, helper_container.size()); 122 | } 123 | 124 | helper_container.clear(); 125 | } break; 126 | 127 | case BENCH_PATTERN_MIXED_ALLOC_FREE: { 128 | std::unordered_map helper_container; 129 | helper_container.reserve(num_elements / 10); 130 | 131 | for (unsigned int i = 0; i < num_elements; i++) { 132 | HLargeObject myLargeObject = pool.allocate_through_init(); 133 | assert(myLargeObject); 134 | 135 | // simulate a very light processing of the allocated item: 136 | myLargeObject->write(10, myLargeObject->read(10) + 2); 137 | myLargeObject->write(20, myLargeObject->read(20) + 2); 138 | 139 | if ((i % 33) == 0) { 140 | // we suddenly realize that we don't really need the just-allocated item... release it immediately 141 | // (it's enough to simply _not_ store it) 142 | } else { 143 | helper_container[i] = myLargeObject; 144 | 145 | // returns to the factory a few items in pseudo-random order 146 | if ((i % 7) == 0 || (i % 31) == 0 || (i % 40) == 0 || (i % 53) == 0) { 147 | size_t value_to_release = i - 1; 148 | 149 | auto it = helper_container.find(value_to_release); 150 | if (it != helper_container.end()) { 151 | // erasing the smart pointer from the std::map will trigger its return to the memory pool: 152 | helper_container.erase(value_to_release); 153 | num_freed++; 154 | } 155 | } 156 | 157 | max_active = std::max(max_active, helper_container.size()); 158 | } 159 | } 160 | } break; 161 | } 162 | } 163 | 164 | static void do_benchmark(json_ctx_t* json_ctx) 165 | { 166 | typedef struct { 167 | unsigned int initial_size; 168 | unsigned int enlarge_step; 169 | unsigned int num_items; 170 | } config_t; 171 | 172 | typedef struct { 173 | BenchPattern_t pattern; 174 | size_t num_configs; 175 | config_t config[10]; // max num configs 176 | } pattern_test_t; 177 | 178 | pattern_test_t testPatterns[] = { 179 | { 180 | BENCH_PATTERN_CONTINUOUS_ALLOCATION, // force newline 181 | 4, // force newline 182 | 183 | { 184 | // run #1 dummy test: a lot of objects and memory pool continuously resizing itself: no gain obtained: 185 | { 1, 1, (int)1e5 }, 186 | 187 | // run #2 slightly better situation: the memory pool has to do 64x less resizings: 188 | { 128, 64, (int)1e5 }, // force newline 189 | 190 | // run #3 much better real-life example: the memory pool starts small but does only a few resizings 191 | // because every time it increases by 1024 items: 192 | { 1024, 1024, (int)1e5 }, // force newline 193 | 194 | // run #4 optimal example: the memory pool starts bigger and does just 7 resizing steps 195 | { 16384, 16384, (int)1e5 }, // force newline 196 | } // force newline 197 | }, 198 | 199 | // another more realistic (???) malloc pattern 200 | 201 | { 202 | BENCH_PATTERN_MIXED_ALLOC_FREE, // force newline 203 | 3, // force newline 204 | 205 | { 206 | // run #5 in this test the memory pool begins small and does several resizings 207 | { 1024, 64, (int)1e5 }, // force newline 208 | 209 | // run #6 in this test the memory pool begins small but does less resizings 210 | { 1024, 128, (int)1e5 }, // force newline 211 | 212 | // run #7 in this test the memory pool begins with already a lot of items, so it does close-to-zero 213 | // resizings: 214 | { 512 * 1024, 1024, (int)1e6 }, // force newline 215 | } // force newline 216 | } 217 | }; 218 | 219 | for (int j = 0; j < sizeof(testPatterns) / sizeof(testPatterns[0]); j++) { 220 | if (json_ctx) { 221 | json_attr_object_begin(json_ctx, (std::string("pattern_") + std::to_string(j + 1)).c_str()); 222 | json_attr_string(json_ctx, "desc", BenchPattern2String(testPatterns[j].pattern).c_str()); 223 | } 224 | 225 | for (int i = 0; i < testPatterns[j].num_configs; i++) { 226 | const config_t& runConfig = testPatterns[j].config[i]; 227 | 228 | size_t num_freed[2], max_active[2], ctor_count[2], dtor_count[2], num_resizings; 229 | struct rusage usage[2]; 230 | timing_t avg_time[2]; 231 | 232 | // run the benchmark with boost_intrusive_pool 233 | { 234 | LargeObject::reset_counts(); 235 | 236 | boost_intrusive_pool real_pool( 237 | runConfig.initial_size /* initial size */, runConfig.enlarge_step /* enlarge step */); 238 | 239 | timing_t start, stop, elapsed, accumulated = 0; 240 | for (int k = 0; k < NUM_AVERAGING_RUNS; k++) { 241 | TIMING_NOW(start); 242 | main_benchmark_loop( 243 | real_pool, testPatterns[j].pattern, runConfig.num_items, num_freed[0], max_active[0]); 244 | TIMING_NOW(stop); 245 | TIMING_DIFF(elapsed, start, stop); 246 | TIMING_ACCUM(accumulated, elapsed); 247 | } 248 | 249 | avg_time[0] = accumulated / NUM_AVERAGING_RUNS; 250 | ctor_count[0] = LargeObject::m_ctor_count; 251 | dtor_count[0] = LargeObject::m_dtor_count; 252 | num_resizings = real_pool.enlarge_steps_done(); 253 | 254 | getrusage(RUSAGE_SELF, &usage[0]); 255 | } 256 | 257 | // run the benchmark with NoPool 258 | { 259 | LargeObject::reset_counts(); 260 | 261 | NoPool comparison_pool; 262 | 263 | timing_t start, stop, elapsed, accumulated = 0; 264 | for (int k = 0; k < NUM_AVERAGING_RUNS; k++) { 265 | TIMING_NOW(start); 266 | main_benchmark_loop( 267 | comparison_pool, testPatterns[j].pattern, runConfig.num_items, num_freed[1], max_active[1]); 268 | TIMING_NOW(stop); 269 | TIMING_DIFF(elapsed, start, stop); 270 | TIMING_ACCUM(accumulated, elapsed); 271 | } 272 | 273 | avg_time[1] = accumulated / NUM_AVERAGING_RUNS; 274 | ctor_count[1] = LargeObject::m_ctor_count; 275 | dtor_count[1] = LargeObject::m_dtor_count; 276 | 277 | getrusage(RUSAGE_SELF, &usage[1]); 278 | } 279 | 280 | // output results as JSON: 281 | if (json_ctx) { 282 | json_attr_object_begin(json_ctx, ("run_" + std::to_string(i + 1)).c_str()); 283 | 284 | // test setup 285 | json_attr_double(json_ctx, "initial_size", runConfig.initial_size); 286 | json_attr_double(json_ctx, "enlarge_step", runConfig.enlarge_step); 287 | json_attr_double(json_ctx, "num_items", runConfig.num_items); 288 | 289 | // test results 290 | json_attr_object_begin(json_ctx, "boost_intrusive_pool"); 291 | json_attr_double(json_ctx, "duration_nsec", avg_time[0]); 292 | json_attr_double(json_ctx, "duration_nsec_per_item", (double)avg_time[0] / (double)runConfig.num_items); 293 | json_attr_double(json_ctx, "num_items_freed", num_freed[0]); 294 | json_attr_double(json_ctx, "max_active_items", max_active[0]); 295 | json_attr_double(json_ctx, "max_rss", usage[0].ru_maxrss); 296 | json_attr_double(json_ctx, "ctor_count", ctor_count[0]); 297 | json_attr_double(json_ctx, "dtor_count", dtor_count[0]); 298 | json_attr_double(json_ctx, "num_resizings", num_resizings); 299 | json_attr_object_end(json_ctx); // boost_intrusive_pool_item 300 | 301 | // test results 302 | json_attr_object_begin(json_ctx, "plain_malloc"); 303 | json_attr_double(json_ctx, "duration_nsec", avg_time[1]); 304 | json_attr_double(json_ctx, "duration_nsec_per_item", (double)avg_time[1] / (double)runConfig.num_items); 305 | json_attr_double(json_ctx, "num_items_freed", num_freed[1]); 306 | json_attr_double(json_ctx, "max_active_items", max_active[1]); 307 | json_attr_double(json_ctx, "max_rss", usage[1].ru_maxrss); 308 | json_attr_double(json_ctx, "ctor_count", ctor_count[1]); 309 | json_attr_double(json_ctx, "dtor_count", dtor_count[1]); 310 | json_attr_object_end(json_ctx); // plain_malloc 311 | json_attr_object_end(json_ctx); // run 312 | } 313 | } 314 | 315 | if (json_ctx) 316 | json_attr_object_end(json_ctx); // run 317 | } 318 | } 319 | 320 | static void do_json_benchmark() 321 | { 322 | json_ctx_t json_ctx; 323 | 324 | json_init(&json_ctx, 0, stdout); 325 | json_document_begin(&json_ctx); 326 | do_benchmark(&json_ctx); 327 | json_document_end(&json_ctx); 328 | 329 | printf("\n"); 330 | } 331 | 332 | static void usage(const char* name) 333 | { 334 | fprintf(stderr, "%s: \n", name); 335 | exit(1); 336 | } 337 | 338 | int main(int argc, char** argv) 339 | { 340 | if (argc > 1) 341 | usage(argv[0]); 342 | 343 | // to better simulate a realistic workload use our own benchmarking routines to 344 | // defrag a little bit the memory of this process (but do not really write any output!) 345 | for (unsigned int i = 0; i < 3; i++) 346 | do_benchmark(NULL); 347 | 348 | // final run is to write real output JSON 349 | do_json_benchmark(); 350 | 351 | return 0; 352 | } 353 | -------------------------------------------------------------------------------- /tests/performance_timing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | typedef uint64_t timing_t; 5 | 6 | #define TIMING_TYPE "clock_gettime" 7 | 8 | /* Measure the resolution of the clock so we can scale the number of 9 | benchmark iterations by this value. */ 10 | #define TIMING_INIT(res) \ 11 | ({ \ 12 | struct timespec start; \ 13 | clock_getres(CLOCK_PROCESS_CPUTIME_ID, &start); \ 14 | (res) = start.tv_nsec; \ 15 | }) 16 | 17 | #define TIMING_NOW(var) \ 18 | ({ \ 19 | struct timespec tv; \ 20 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tv); \ 21 | (var) = (uint64_t)(tv.tv_nsec + (uint64_t)1000000000 * tv.tv_sec); \ 22 | }) 23 | 24 | #define TIMING_DIFF(diff, start, end) (diff) = (end) - (start) 25 | #define TIMING_ACCUM(sum, diff) (sum) += (diff) 26 | 27 | #define TIMING_PRINT_MEAN(d_total_s, d_iters) printf("\t%g", (d_total_s) / (d_iters)) 28 | -------------------------------------------------------------------------------- /tests/results/bench_results_gnulibc.json: -------------------------------------------------------------------------------- 1 | { 2 | "pattern_1": { 3 | "desc": "Continuous allocations, bulk free at end", 4 | "run_1": { 5 | "initial_size": 1, 6 | "enlarge_step": 1, 7 | "num_items": 100000, 8 | "boost_intrusive_pool": { 9 | "duration_nsec": 1.24627e+07, 10 | "duration_nsec_per_item": 124.627, 11 | "num_items_freed": 0, 12 | "max_active_items": 100000, 13 | "max_rss": 845756, 14 | "ctor_count": 100001, 15 | "dtor_count": 0, 16 | "num_resizings": 100001 17 | }, 18 | "plain_malloc": { 19 | "duration_nsec": 1.36862e+07, 20 | "duration_nsec_per_item": 136.862, 21 | "num_items_freed": 0, 22 | "max_active_items": 100000, 23 | "max_rss": 845756, 24 | "ctor_count": 1e+06, 25 | "dtor_count": 1e+06 26 | } 27 | }, 28 | "run_2": { 29 | "initial_size": 128, 30 | "enlarge_step": 64, 31 | "num_items": 100000, 32 | "boost_intrusive_pool": { 33 | "duration_nsec": 1.10467e+07, 34 | "duration_nsec_per_item": 110.467, 35 | "num_items_freed": 0, 36 | "max_active_items": 100000, 37 | "max_rss": 845756, 38 | "ctor_count": 100032, 39 | "dtor_count": 0, 40 | "num_resizings": 1562 41 | }, 42 | "plain_malloc": { 43 | "duration_nsec": 1.40312e+07, 44 | "duration_nsec_per_item": 140.312, 45 | "num_items_freed": 0, 46 | "max_active_items": 100000, 47 | "max_rss": 845756, 48 | "ctor_count": 1e+06, 49 | "dtor_count": 1e+06 50 | } 51 | }, 52 | "run_3": { 53 | "initial_size": 1024, 54 | "enlarge_step": 1024, 55 | "num_items": 100000, 56 | "boost_intrusive_pool": { 57 | "duration_nsec": 1.1183e+07, 58 | "duration_nsec_per_item": 111.83, 59 | "num_items_freed": 0, 60 | "max_active_items": 100000, 61 | "max_rss": 845756, 62 | "ctor_count": 100352, 63 | "dtor_count": 0, 64 | "num_resizings": 98 65 | }, 66 | "plain_malloc": { 67 | "duration_nsec": 1.46498e+07, 68 | "duration_nsec_per_item": 146.498, 69 | "num_items_freed": 0, 70 | "max_active_items": 100000, 71 | "max_rss": 845756, 72 | "ctor_count": 1e+06, 73 | "dtor_count": 1e+06 74 | } 75 | }, 76 | "run_4": { 77 | "initial_size": 16384, 78 | "enlarge_step": 16384, 79 | "num_items": 100000, 80 | "boost_intrusive_pool": { 81 | "duration_nsec": 1.13708e+07, 82 | "duration_nsec_per_item": 113.708, 83 | "num_items_freed": 0, 84 | "max_active_items": 100000, 85 | "max_rss": 845756, 86 | "ctor_count": 114688, 87 | "dtor_count": 0, 88 | "num_resizings": 7 89 | }, 90 | "plain_malloc": { 91 | "duration_nsec": 1.36612e+07, 92 | "duration_nsec_per_item": 136.612, 93 | "num_items_freed": 0, 94 | "max_active_items": 100000, 95 | "max_rss": 845756, 96 | "ctor_count": 1e+06, 97 | "dtor_count": 1e+06 98 | } 99 | } 100 | }, 101 | "pattern_2": { 102 | "desc": "Mixed alloc/free pattern", 103 | "run_1": { 104 | "initial_size": 1024, 105 | "enlarge_step": 64, 106 | "num_items": 100000, 107 | "boost_intrusive_pool": { 108 | "duration_nsec": 2.43862e+07, 109 | "duration_nsec_per_item": 243.862, 110 | "num_items_freed": 19393, 111 | "max_active_items": 77576, 112 | "max_rss": 845756, 113 | "ctor_count": 77632, 114 | "dtor_count": 0, 115 | "num_resizings": 1198 116 | }, 117 | "plain_malloc": { 118 | "duration_nsec": 2.7661e+07, 119 | "duration_nsec_per_item": 276.61, 120 | "num_items_freed": 19393, 121 | "max_active_items": 77576, 122 | "max_rss": 845756, 123 | "ctor_count": 1e+06, 124 | "dtor_count": 1e+06 125 | } 126 | }, 127 | "run_2": { 128 | "initial_size": 1024, 129 | "enlarge_step": 128, 130 | "num_items": 100000, 131 | "boost_intrusive_pool": { 132 | "duration_nsec": 2.31621e+07, 133 | "duration_nsec_per_item": 231.621, 134 | "num_items_freed": 19393, 135 | "max_active_items": 77576, 136 | "max_rss": 845756, 137 | "ctor_count": 77696, 138 | "dtor_count": 0, 139 | "num_resizings": 600 140 | }, 141 | "plain_malloc": { 142 | "duration_nsec": 2.69408e+07, 143 | "duration_nsec_per_item": 269.408, 144 | "num_items_freed": 19393, 145 | "max_active_items": 77576, 146 | "max_rss": 845756, 147 | "ctor_count": 1e+06, 148 | "dtor_count": 1e+06 149 | } 150 | }, 151 | "run_3": { 152 | "initial_size": 524288, 153 | "enlarge_step": 1024, 154 | "num_items": 1e+06, 155 | "boost_intrusive_pool": { 156 | "duration_nsec": 2.42851e+08, 157 | "duration_nsec_per_item": 242.851, 158 | "num_items_freed": 193987, 159 | "max_active_items": 775709, 160 | "max_rss": 845756, 161 | "ctor_count": 776192, 162 | "dtor_count": 0, 163 | "num_resizings": 247 164 | }, 165 | "plain_malloc": { 166 | "duration_nsec": 3.28475e+08, 167 | "duration_nsec_per_item": 328.475, 168 | "num_items_freed": 193987, 169 | "max_active_items": 775709, 170 | "max_rss": 845756, 171 | "ctor_count": 1e+07, 172 | "dtor_count": 1e+07 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/results/bench_results_jemalloc.json: -------------------------------------------------------------------------------- 1 | { 2 | "pattern_1": { 3 | "desc": "Continuous allocations, bulk free at end", 4 | "run_1": { 5 | "initial_size": 1, 6 | "enlarge_step": 1, 7 | "num_items": 100000, 8 | "boost_intrusive_pool": { 9 | "duration_nsec": 1.38833e+07, 10 | "duration_nsec_per_item": 138.833, 11 | "num_items_freed": 0, 12 | "max_active_items": 100000, 13 | "max_rss": 1.01415e+06, 14 | "ctor_count": 100001, 15 | "dtor_count": 0, 16 | "num_resizings": 100001 17 | }, 18 | "plain_malloc": { 19 | "duration_nsec": 4.17634e+07, 20 | "duration_nsec_per_item": 417.634, 21 | "num_items_freed": 0, 22 | "max_active_items": 100000, 23 | "max_rss": 1.01415e+06, 24 | "ctor_count": 1e+06, 25 | "dtor_count": 1e+06 26 | } 27 | }, 28 | "run_2": { 29 | "initial_size": 128, 30 | "enlarge_step": 64, 31 | "num_items": 100000, 32 | "boost_intrusive_pool": { 33 | "duration_nsec": 1.19925e+07, 34 | "duration_nsec_per_item": 119.925, 35 | "num_items_freed": 0, 36 | "max_active_items": 100000, 37 | "max_rss": 1.01415e+06, 38 | "ctor_count": 100032, 39 | "dtor_count": 0, 40 | "num_resizings": 1562 41 | }, 42 | "plain_malloc": { 43 | "duration_nsec": 4.14359e+07, 44 | "duration_nsec_per_item": 414.359, 45 | "num_items_freed": 0, 46 | "max_active_items": 100000, 47 | "max_rss": 1.01415e+06, 48 | "ctor_count": 1e+06, 49 | "dtor_count": 1e+06 50 | } 51 | }, 52 | "run_3": { 53 | "initial_size": 1024, 54 | "enlarge_step": 1024, 55 | "num_items": 100000, 56 | "boost_intrusive_pool": { 57 | "duration_nsec": 1.17515e+07, 58 | "duration_nsec_per_item": 117.515, 59 | "num_items_freed": 0, 60 | "max_active_items": 100000, 61 | "max_rss": 1.01415e+06, 62 | "ctor_count": 100352, 63 | "dtor_count": 0, 64 | "num_resizings": 98 65 | }, 66 | "plain_malloc": { 67 | "duration_nsec": 4.13884e+07, 68 | "duration_nsec_per_item": 413.884, 69 | "num_items_freed": 0, 70 | "max_active_items": 100000, 71 | "max_rss": 1.01415e+06, 72 | "ctor_count": 1e+06, 73 | "dtor_count": 1e+06 74 | } 75 | }, 76 | "run_4": { 77 | "initial_size": 16384, 78 | "enlarge_step": 16384, 79 | "num_items": 100000, 80 | "boost_intrusive_pool": { 81 | "duration_nsec": 1.23111e+07, 82 | "duration_nsec_per_item": 123.111, 83 | "num_items_freed": 0, 84 | "max_active_items": 100000, 85 | "max_rss": 1.01415e+06, 86 | "ctor_count": 114688, 87 | "dtor_count": 0, 88 | "num_resizings": 7 89 | }, 90 | "plain_malloc": { 91 | "duration_nsec": 4.13518e+07, 92 | "duration_nsec_per_item": 413.518, 93 | "num_items_freed": 0, 94 | "max_active_items": 100000, 95 | "max_rss": 1.01415e+06, 96 | "ctor_count": 1e+06, 97 | "dtor_count": 1e+06 98 | } 99 | } 100 | }, 101 | "pattern_2": { 102 | "desc": "Mixed alloc/free pattern", 103 | "run_1": { 104 | "initial_size": 1024, 105 | "enlarge_step": 64, 106 | "num_items": 100000, 107 | "boost_intrusive_pool": { 108 | "duration_nsec": 2.37305e+07, 109 | "duration_nsec_per_item": 237.305, 110 | "num_items_freed": 19393, 111 | "max_active_items": 77576, 112 | "max_rss": 1.01415e+06, 113 | "ctor_count": 77632, 114 | "dtor_count": 0, 115 | "num_resizings": 1198 116 | }, 117 | "plain_malloc": { 118 | "duration_nsec": 4.58603e+07, 119 | "duration_nsec_per_item": 458.603, 120 | "num_items_freed": 19393, 121 | "max_active_items": 77576, 122 | "max_rss": 1.01415e+06, 123 | "ctor_count": 1e+06, 124 | "dtor_count": 1e+06 125 | } 126 | }, 127 | "run_2": { 128 | "initial_size": 1024, 129 | "enlarge_step": 128, 130 | "num_items": 100000, 131 | "boost_intrusive_pool": { 132 | "duration_nsec": 2.33321e+07, 133 | "duration_nsec_per_item": 233.321, 134 | "num_items_freed": 19393, 135 | "max_active_items": 77576, 136 | "max_rss": 1.01415e+06, 137 | "ctor_count": 77696, 138 | "dtor_count": 0, 139 | "num_resizings": 600 140 | }, 141 | "plain_malloc": { 142 | "duration_nsec": 4.60406e+07, 143 | "duration_nsec_per_item": 460.406, 144 | "num_items_freed": 19393, 145 | "max_active_items": 77576, 146 | "max_rss": 1.01415e+06, 147 | "ctor_count": 1e+06, 148 | "dtor_count": 1e+06 149 | } 150 | }, 151 | "run_3": { 152 | "initial_size": 524288, 153 | "enlarge_step": 1024, 154 | "num_items": 1e+06, 155 | "boost_intrusive_pool": { 156 | "duration_nsec": 2.70221e+08, 157 | "duration_nsec_per_item": 270.221, 158 | "num_items_freed": 193987, 159 | "max_active_items": 775709, 160 | "max_rss": 1.01415e+06, 161 | "ctor_count": 776192, 162 | "dtor_count": 0, 163 | "num_resizings": 247 164 | }, 165 | "plain_malloc": { 166 | "duration_nsec": 4.71626e+08, 167 | "duration_nsec_per_item": 471.626, 168 | "num_items_freed": 193987, 169 | "max_active_items": 775709, 170 | "max_rss": 1.0145e+06, 171 | "ctor_count": 1e+07, 172 | "dtor_count": 1e+07 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/results/bench_results_tcmalloc.json: -------------------------------------------------------------------------------- 1 | { 2 | "pattern_1": { 3 | "desc": "Continuous allocations, bulk free at end", 4 | "run_1": { 5 | "initial_size": 1, 6 | "enlarge_step": 1, 7 | "num_items": 100000, 8 | "boost_intrusive_pool": { 9 | "duration_nsec": 1.11575e+07, 10 | "duration_nsec_per_item": 111.575, 11 | "num_items_freed": 0, 12 | "max_active_items": 100000, 13 | "max_rss": 1.07762e+06, 14 | "ctor_count": 100001, 15 | "dtor_count": 0, 16 | "num_resizings": 100001 17 | }, 18 | "plain_malloc": { 19 | "duration_nsec": 2.43287e+07, 20 | "duration_nsec_per_item": 243.287, 21 | "num_items_freed": 0, 22 | "max_active_items": 100000, 23 | "max_rss": 1.07762e+06, 24 | "ctor_count": 1e+06, 25 | "dtor_count": 1e+06 26 | } 27 | }, 28 | "run_2": { 29 | "initial_size": 128, 30 | "enlarge_step": 64, 31 | "num_items": 100000, 32 | "boost_intrusive_pool": { 33 | "duration_nsec": 1.14217e+07, 34 | "duration_nsec_per_item": 114.217, 35 | "num_items_freed": 0, 36 | "max_active_items": 100000, 37 | "max_rss": 1.07762e+06, 38 | "ctor_count": 100032, 39 | "dtor_count": 0, 40 | "num_resizings": 1562 41 | }, 42 | "plain_malloc": { 43 | "duration_nsec": 2.81842e+07, 44 | "duration_nsec_per_item": 281.842, 45 | "num_items_freed": 0, 46 | "max_active_items": 100000, 47 | "max_rss": 1.07762e+06, 48 | "ctor_count": 1e+06, 49 | "dtor_count": 1e+06 50 | } 51 | }, 52 | "run_3": { 53 | "initial_size": 1024, 54 | "enlarge_step": 1024, 55 | "num_items": 100000, 56 | "boost_intrusive_pool": { 57 | "duration_nsec": 1.18054e+07, 58 | "duration_nsec_per_item": 118.054, 59 | "num_items_freed": 0, 60 | "max_active_items": 100000, 61 | "max_rss": 1.07762e+06, 62 | "ctor_count": 100352, 63 | "dtor_count": 0, 64 | "num_resizings": 98 65 | }, 66 | "plain_malloc": { 67 | "duration_nsec": 2.77235e+07, 68 | "duration_nsec_per_item": 277.235, 69 | "num_items_freed": 0, 70 | "max_active_items": 100000, 71 | "max_rss": 1.07762e+06, 72 | "ctor_count": 1e+06, 73 | "dtor_count": 1e+06 74 | } 75 | }, 76 | "run_4": { 77 | "initial_size": 16384, 78 | "enlarge_step": 16384, 79 | "num_items": 100000, 80 | "boost_intrusive_pool": { 81 | "duration_nsec": 1.16231e+07, 82 | "duration_nsec_per_item": 116.231, 83 | "num_items_freed": 0, 84 | "max_active_items": 100000, 85 | "max_rss": 1.07762e+06, 86 | "ctor_count": 114688, 87 | "dtor_count": 0, 88 | "num_resizings": 7 89 | }, 90 | "plain_malloc": { 91 | "duration_nsec": 2.74368e+07, 92 | "duration_nsec_per_item": 274.368, 93 | "num_items_freed": 0, 94 | "max_active_items": 100000, 95 | "max_rss": 1.07762e+06, 96 | "ctor_count": 1e+06, 97 | "dtor_count": 1e+06 98 | } 99 | } 100 | }, 101 | "pattern_2": { 102 | "desc": "Mixed alloc/free pattern", 103 | "run_1": { 104 | "initial_size": 1024, 105 | "enlarge_step": 64, 106 | "num_items": 100000, 107 | "boost_intrusive_pool": { 108 | "duration_nsec": 2.21854e+07, 109 | "duration_nsec_per_item": 221.854, 110 | "num_items_freed": 19393, 111 | "max_active_items": 77576, 112 | "max_rss": 1.07762e+06, 113 | "ctor_count": 77632, 114 | "dtor_count": 0, 115 | "num_resizings": 1198 116 | }, 117 | "plain_malloc": { 118 | "duration_nsec": 2.94664e+07, 119 | "duration_nsec_per_item": 294.664, 120 | "num_items_freed": 19393, 121 | "max_active_items": 77576, 122 | "max_rss": 1.07762e+06, 123 | "ctor_count": 1e+06, 124 | "dtor_count": 1e+06 125 | } 126 | }, 127 | "run_2": { 128 | "initial_size": 1024, 129 | "enlarge_step": 128, 130 | "num_items": 100000, 131 | "boost_intrusive_pool": { 132 | "duration_nsec": 2.28575e+07, 133 | "duration_nsec_per_item": 228.575, 134 | "num_items_freed": 19393, 135 | "max_active_items": 77576, 136 | "max_rss": 1.07762e+06, 137 | "ctor_count": 77696, 138 | "dtor_count": 0, 139 | "num_resizings": 600 140 | }, 141 | "plain_malloc": { 142 | "duration_nsec": 3.11666e+07, 143 | "duration_nsec_per_item": 311.666, 144 | "num_items_freed": 19393, 145 | "max_active_items": 77576, 146 | "max_rss": 1.07762e+06, 147 | "ctor_count": 1e+06, 148 | "dtor_count": 1e+06 149 | } 150 | }, 151 | "run_3": { 152 | "initial_size": 524288, 153 | "enlarge_step": 1024, 154 | "num_items": 1e+06, 155 | "boost_intrusive_pool": { 156 | "duration_nsec": 2.49513e+08, 157 | "duration_nsec_per_item": 249.513, 158 | "num_items_freed": 193987, 159 | "max_active_items": 775709, 160 | "max_rss": 1.07762e+06, 161 | "ctor_count": 776192, 162 | "dtor_count": 0, 163 | "num_resizings": 247 164 | }, 165 | "plain_malloc": { 166 | "duration_nsec": 9.48147e+08, 167 | "duration_nsec_per_item": 948.147, 168 | "num_items_freed": 193987, 169 | "max_active_items": 775709, 170 | "max_rss": 1.07762e+06, 171 | "ctor_count": 1e+07, 172 | "dtor_count": 1e+07 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/results/pattern_1_gnulibc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_1_gnulibc.png -------------------------------------------------------------------------------- /tests/results/pattern_1_jemalloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_1_jemalloc.png -------------------------------------------------------------------------------- /tests/results/pattern_1_tcmalloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_1_tcmalloc.png -------------------------------------------------------------------------------- /tests/results/pattern_2_gnulibc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_2_gnulibc.png -------------------------------------------------------------------------------- /tests/results/pattern_2_jemalloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_2_jemalloc.png -------------------------------------------------------------------------------- /tests/results/pattern_2_tcmalloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f18m/boost-intrusive-pool/73536083ce541675b9901176b9315c588907fe0c/tests/results/pattern_2_tcmalloc.png -------------------------------------------------------------------------------- /tests/tracing_malloc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | #define TRACE_METHOD() std::cout << "[Executing " << __PRETTY_FUNCTION__ << " for instance=" << this << "]\n"; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Utility functions to trace mallocs 10 | //------------------------------------------------------------------------------ 11 | 12 | // replace operator new and delete to log allocations 13 | void* operator new(std::size_t n) 14 | { 15 | void* ret = malloc(n); 16 | std::cout << "[Allocating " << n << "bytes: " << ret << "]" << std::endl; 17 | return ret; 18 | } 19 | 20 | void operator delete(void* p) throw() 21 | { 22 | std::cout << "[Freeing " << p << "]" << std::endl; 23 | free(p); 24 | } 25 | 26 | void operator delete(void* p, size_t n) throw() 27 | { 28 | std::cout << "[Freeing " << p << "]" << std::endl; 29 | free(p); 30 | } 31 | 32 | void print_header() 33 | { 34 | std::cout << "**************************************************************************************" << std::endl; 35 | } 36 | -------------------------------------------------------------------------------- /tests/tutorial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Small example program to show the advantage of the 3 | * boost_intrusive_pool compared to e.g. direct use of 4 | * std::shared_ptr<> 5 | * 6 | * Author: fmontorsi 7 | * Created: Feb 2019 8 | * License: BSD license 9 | * 10 | */ 11 | 12 | //------------------------------------------------------------------------------ 13 | // Includes 14 | //------------------------------------------------------------------------------ 15 | 16 | #include "boost_intrusive_pool.hpp" 17 | #include "tracing_malloc.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | //------------------------------------------------------------------------------ 29 | // Utility classes 30 | //------------------------------------------------------------------------------ 31 | 32 | class DummyClass : public memorypool::boost_intrusive_pool_item { 33 | public: 34 | DummyClass(uint32_t n = 0) 35 | { 36 | buf[0] = 'a' + (n & 0x11); 37 | buf[1023] = (n >> 24); 38 | TRACE_METHOD(); 39 | } 40 | ~DummyClass() 41 | { 42 | TRACE_METHOD(); // this shows that dtor runs every time an instance returns to the pool! 43 | } 44 | 45 | virtual char dummy() const { return buf[0]; } 46 | void init(uint32_t n = 0) { buf[0] = 'a' + (n & 0x11); } 47 | 48 | private: 49 | // just some fat buffer: 50 | char buf[1024]; 51 | }; 52 | 53 | class DummyDerivedClass : public DummyClass { 54 | public: 55 | DummyDerivedClass(uint32_t n = 0) 56 | : DummyClass(n) 57 | { 58 | bag[0] = 'A' + n; 59 | TRACE_METHOD(); 60 | } 61 | ~DummyDerivedClass() 62 | { 63 | TRACE_METHOD(); // this shows that dtor runs every time an instance returns to the pool! 64 | } 65 | 66 | virtual char dummy() const { return (char)bag[0]; } 67 | void init(uint32_t n = 0) { bag[0] = 'A' + n; } 68 | void yet_another_init_fun(uint32_t n = 0) { bag[0] = 'A' + n; } 69 | 70 | private: 71 | // yet another fat buffer: 72 | size_t bag[2048]; 73 | }; 74 | 75 | //------------------------------------------------------------------------------ 76 | // Utility functions 77 | //------------------------------------------------------------------------------ 78 | 79 | template void observe_pool(const memorypool::boost_intrusive_pool& pool) 80 | { 81 | std::cout << " The pool now has capacity=" << pool.capacity() << ", unused_count=" << (size_t)pool.unused_count() 82 | << ", inuse_count=" << pool.inuse_count() << std::endl; 83 | } 84 | 85 | //------------------------------------------------------------------------------ 86 | // Showcase routines 87 | //------------------------------------------------------------------------------ 88 | 89 | void showcase_std_shared_pointers() 90 | { 91 | print_header(); 92 | std::cout << "Running some examples for std::shared_ptr<>:" << std::endl; 93 | 94 | // the test below shows that the overhead of a std::shared_ptr is about 8B/pointer 95 | // (tested with GCC 7.3.0) and the control block adds an overhead of about 16B 96 | 97 | { 98 | std::shared_ptr hdummy; 99 | std::cout << " Size of a simple std::shared_ptr<>: " << sizeof(hdummy) << std::endl; 100 | 101 | { 102 | // now allocate an instance of the class: 103 | std::cout << " Now allocating dummy class of size: " << sizeof(DummyClass) << std::endl; 104 | hdummy = std::make_shared(); // you will see a memory malloc traced when running this line 105 | 106 | // of course copying pointers around does not trigger any memory allocation: 107 | std::shared_ptr hdummy2 = hdummy; 108 | 109 | } // the DummyClass instance survives this block since its reference is stored in hdummy 110 | 111 | std::cout << " Going to release all references to the std::shared_ptr<> created so far" << std::endl; 112 | } // you will see a memory free traced when running this line 113 | } 114 | 115 | void showcase_boost_intrusive_pointers() 116 | { 117 | print_header(); 118 | std::cout << "Running some examples for boost::intrusive_ptr<>:" << std::endl; 119 | 120 | { 121 | std::cout << " Now allocating dummy class of size: " << sizeof(DummyClass) << std::endl; 122 | 123 | boost::intrusive_ptr hdummy(new DummyClass(3)); 124 | // you will see a memory malloc traced when running this line 125 | 126 | std::cout << " Size of a simple boost::intrusive_ptr<>: " << sizeof(hdummy) << std::endl; 127 | 128 | { 129 | // of course copying pointers around does not trigger any memory allocation: 130 | boost::intrusive_ptr hdummy2 = hdummy; 131 | std::cout << " Value from allocated dummy class: " << hdummy2->dummy() << std::endl; 132 | 133 | // of course you can move Boost intrusive pointers as well: 134 | std::cout << " Before std::move hdummy2 is valid: " << (bool)hdummy2 << std::endl; 135 | boost::intrusive_ptr hdummy3(std::move(hdummy2)); 136 | std::cout << " Value from allocated dummy class: " << hdummy3->dummy() << std::endl; 137 | std::cout << " After std::move hdummy2 is valid: " << (bool)hdummy2 << std::endl; 138 | 139 | } // the DummyClass instance survives this block since its reference is stored in hdummy 140 | 141 | std::cout << " Going to release all references to the boost::intrusive_ptr<> created so far" << std::endl; 142 | } // you will see a memory free traced when running this line 143 | } 144 | 145 | void showcase_boost_intrusive_pool() 146 | { 147 | print_header(); 148 | 149 | { 150 | std::cout << " Now allocating a new memorypool::boost_intrusive_pool<>. A large malloc will happen." 151 | << std::endl; 152 | 153 | memorypool::boost_intrusive_pool pool(4, 1); 154 | // you will see a memory malloc traced when running this line 155 | 156 | std::cout << " Boost Intrusive Pool for DummyClass has size: " << sizeof(pool) << std::endl; 157 | 158 | { 159 | std::cout << " Now allocating dummy class of size: " << sizeof(DummyClass) 160 | << " from the memory pool. This time no mallocs will happen!" << std::endl; 161 | boost::intrusive_ptr hdummy = pool.allocate(); 162 | 163 | // of course copying pointers around does not trigger any memory allocation: 164 | boost::intrusive_ptr hdummy2 = hdummy; 165 | std::cout << " Value from allocated dummy class constructed via default ctor: " << hdummy2->dummy() 166 | << std::endl; 167 | 168 | observe_pool(pool); 169 | 170 | std::cout << " Going to release the references to the boost::intrusive_ptr<> created so far. This time no " 171 | "free() will happen!" 172 | << std::endl; 173 | 174 | } // this time no memory free() will happen! 175 | 176 | observe_pool(pool); 177 | 178 | std::cout << " Going to release the whole memory pool. You will see a bunch of dtor and memory free happen!" 179 | << std::endl; 180 | } // you will see a memory free traced when running this line 181 | 182 | print_header(); 183 | 184 | // now showcase a memory pool of objects having the following class hierarchy: 185 | // boost_intrusive_pool_item 186 | // \--- DummyClass 187 | // \--- DummyDerivedClass 188 | 189 | { 190 | std::cout << " Now allocating a new memorypool::boost_intrusive_pool<>. A large malloc will happen." 191 | << std::endl; 192 | memorypool::boost_intrusive_pool pool(4, 1); 193 | 194 | { 195 | std::cout << " Now allocating derived dummy class of size: " << sizeof(DummyDerivedClass) 196 | << " from the memory pool. This time no mallocs will happen but the ctor will get called again!" 197 | << std::endl; 198 | 199 | size_t initializer_value = 3; 200 | 201 | auto custom_init_fn 202 | = [&initializer_value](DummyDerivedClass& object) { object.yet_another_init_fun(initializer_value); }; 203 | 204 | // just for fun this time we allocate the object using non-default constructor: 205 | boost::intrusive_ptr hdummy = pool.allocate_through_function(custom_init_fn); 206 | 207 | std::cout << " Value from allocated dummy class constructed via NON default ctor: " << hdummy->dummy() 208 | << std::endl; 209 | 210 | observe_pool(pool); 211 | 212 | std::cout << " Going to release the references to the boost::intrusive_ptr<> created so far. This time no " 213 | "free() will happen, just a dtor call!" 214 | << std::endl; 215 | 216 | } // this time no memory free() will happen! 217 | 218 | observe_pool(pool); 219 | 220 | std::cout << " Going to release the whole memory pool. You will see a bunch of dtor and memory free happen!" 221 | << std::endl; 222 | } // you will see a memory free traced when running this line 223 | 224 | std::cout << "Note that the overhead of memory pool support is sizeof(memorypool::boost_intrusive_pool_item)=" 225 | << sizeof(memorypool::boost_intrusive_pool_item) << "bytes" << std::endl; 226 | } 227 | 228 | //------------------------------------------------------------------------------ 229 | // Main 230 | //------------------------------------------------------------------------------ 231 | 232 | int main() 233 | { 234 | showcase_std_shared_pointers(); 235 | showcase_boost_intrusive_pointers(); 236 | showcase_boost_intrusive_pool(); 237 | 238 | print_header(); 239 | std::cout << "Exiting" << std::endl; 240 | return 0; 241 | } 242 | -------------------------------------------------------------------------------- /tests/unit_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Set of unit tests for the memorypool::boost_intrusive_pool 3 | * Some unit tests have been adapted from: 4 | * https://github.com/steinwurf/recycle 5 | * 6 | * Note that to be able to take advantage of TravisCI/github integration 7 | * we only use features of Boost.test up to version 1.58 8 | * 9 | * Author: fmontorsi 10 | * Created: Feb 2019 11 | * License: BSD license 12 | * 13 | */ 14 | 15 | //------------------------------------------------------------------------------ 16 | // Includes 17 | //------------------------------------------------------------------------------ 18 | 19 | #define BOOST_INTRUSIVE_POOL_DEBUG_CHECKS 1 20 | #include "boost_intrusive_pool.hpp" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define BOOST_REQUIRE_MODULE "main4" 31 | #include 32 | 33 | using namespace memorypool; 34 | 35 | //------------------------------------------------------------------------------ 36 | // Dummy test objects 37 | //------------------------------------------------------------------------------ 38 | 39 | class DummyInt : public boost_intrusive_pool_item { 40 | public: 41 | DummyInt(int j = 0) 42 | : m_int(j) 43 | { 44 | } 45 | 46 | DummyInt(const DummyInt&) = default; 47 | 48 | void init(int j = 0) { m_int = j; } 49 | void destroy() { m_int = 0; } 50 | 51 | bool operator==(const DummyInt& x) const { return x.m_int == m_int; } 52 | 53 | private: 54 | int m_int; 55 | }; 56 | 57 | typedef boost::intrusive_ptr HDummyInt; 58 | 59 | // Default constructible dummy object 60 | struct dummy_one : public boost_intrusive_pool_item { 61 | dummy_one() { ++m_count; } 62 | 63 | ~dummy_one() { --m_count; } 64 | 65 | void init() { } 66 | 67 | // Counter which will check how many object have been allocate 68 | // and deallocated 69 | static int32_t m_count; 70 | }; 71 | 72 | int32_t dummy_one::m_count = 0; 73 | 74 | void recycle_dummy_one(boost::intrusive_ptr p) 75 | { /* nothing to do actually */ 76 | } 77 | 78 | // Non Default constructible dummy object 79 | struct dummy_two : public boost_intrusive_pool_item { 80 | dummy_two(uint32_t useless = 0) { ++m_count; } 81 | 82 | ~dummy_two() { --m_count; } 83 | 84 | void init(uint32_t useless = 0) { } 85 | 86 | static int32_t m_count; 87 | }; 88 | 89 | int32_t dummy_two::m_count = 0; 90 | 91 | void recycle_dummy_two(boost::intrusive_ptr p) 92 | { /* nothing to do actually */ 93 | } 94 | 95 | // enable_shared_from_this dummy object 96 | struct dummy_three : std::enable_shared_from_this, public boost_intrusive_pool_item { 97 | 98 | dummy_three() { ++m_count; } 99 | 100 | ~dummy_three() { --m_count; } 101 | 102 | // Counter which will check how many object have been allocate 103 | // and deallocated 104 | static int32_t m_count; 105 | }; 106 | 107 | int32_t dummy_three::m_count = 0; 108 | 109 | //------------------------------------------------------------------------------ 110 | // Actual testcases 111 | //------------------------------------------------------------------------------ 112 | 113 | void infinite_memory_pool() 114 | { 115 | BOOST_TEST_MESSAGE("Starting boost_intrusive_pool<> tests when memory pool is unbounded"); 116 | 117 | struct { 118 | unsigned int initial_size; 119 | unsigned int enlarge_step; 120 | unsigned int num_elements; 121 | } testArray[] = { 122 | { 10, 1, (int)1e6 }, // force newline 123 | { 1, 100, (int)5e4 }, // force newline 124 | { 100000, 1, 123456 }, // force newline 125 | }; 126 | 127 | for (int testIdx = 0; testIdx < sizeof(testArray) / sizeof(testArray[0]); testIdx++) { 128 | BOOST_TEST_MESSAGE(std::string("Starting test entry #") + std::to_string(testIdx)); 129 | 130 | boost_intrusive_pool f( 131 | testArray[testIdx].initial_size /* initial size */, testArray[testIdx].enlarge_step /* enlarge step */); 132 | 133 | BOOST_REQUIRE(!f.is_bounded()); 134 | f.check(); 135 | 136 | size_t num_freed = 0, max_active = 0; 137 | std::map helper_container; 138 | for (unsigned int j = 0; j < testArray[testIdx].num_elements; j++) { 139 | HDummyInt myInt = f.allocate_through_init(j); 140 | assert(myInt); 141 | 142 | f.check(); 143 | 144 | *myInt = j; 145 | helper_container[j] = myInt; 146 | 147 | // returns to the factory a few items in pseudo-random order 148 | if ((j % 7) == 0 || (j % 53) == 0 || (j % 12345) == 0) { 149 | size_t value_to_release = j / 10; 150 | 151 | auto it = helper_container.find(value_to_release); 152 | if (it != helper_container.end()) { 153 | // erasing the smart pointer from the std::map will trigger its return to the memory pool: 154 | helper_container.erase(value_to_release); 155 | num_freed++; 156 | } 157 | } 158 | 159 | max_active = std::max(max_active, helper_container.size()); 160 | } 161 | 162 | f.check(); 163 | 164 | BOOST_REQUIRE(!f.is_memory_exhausted()); 165 | BOOST_REQUIRE(num_freed > 0); 166 | 167 | // This should hold always: 168 | // m_free_count+m_inuse_count == m_total_count 169 | BOOST_REQUIRE_EQUAL(f.unused_count() + f.inuse_count(), f.capacity()); 170 | 171 | BOOST_REQUIRE_EQUAL(f.inuse_count(), testArray[testIdx].num_elements - num_freed); 172 | BOOST_REQUIRE(f.capacity() >= max_active); 173 | BOOST_REQUIRE(!f.empty()); 174 | 175 | if (testArray[testIdx].enlarge_step > 1) 176 | BOOST_REQUIRE(f.unused_count() > 0); 177 | 178 | if (testArray[testIdx].initial_size < testArray[testIdx].num_elements - num_freed) 179 | BOOST_REQUIRE(f.enlarge_steps_done() > 0); 180 | 181 | // IMPORTANT: this will crash as all pointers inside the map are INVALIDATED before 182 | // the map clear() is called: 183 | /* 184 | f.clear(); 185 | helper_container.clear(); // all pointers it contains have been invalidated! 186 | */ 187 | 188 | helper_container.clear(); // all pointers it contains will be now released 189 | 190 | // repeat twice for test: 191 | f.clear(); 192 | f.clear(); 193 | 194 | f.check(); 195 | 196 | BOOST_REQUIRE_EQUAL(f.inuse_count(), 0); 197 | BOOST_REQUIRE_EQUAL(f.capacity(), 0); 198 | BOOST_REQUIRE(f.empty()); 199 | BOOST_REQUIRE_EQUAL(f.unused_count(), 0); 200 | } 201 | } 202 | 203 | void bounded_memory_pool() 204 | { 205 | BOOST_TEST_MESSAGE("Starting boost_intrusive_pool<> tests when memory pool is bounded"); 206 | 207 | struct { 208 | unsigned int initial_size; 209 | } testArray[] = { 210 | { 1 }, // force newline 211 | { 10 }, // force newline 212 | { 100000 }, // force newline 213 | }; 214 | 215 | for (int testIdx = 0; testIdx < sizeof(testArray) / sizeof(testArray[0]); testIdx++) { 216 | BOOST_TEST_MESSAGE(std::string("Starting test entry #") + std::to_string(testIdx)); 217 | 218 | boost_intrusive_pool f(testArray[testIdx].initial_size /* initial size */, 0 /* enlarge step */); 219 | std::vector helper_container; 220 | 221 | BOOST_REQUIRE(f.is_bounded()); 222 | 223 | for (unsigned int j = 0; j < testArray[testIdx].initial_size; j++) { 224 | HDummyInt myInt = f.allocate_through_init(3); 225 | assert(myInt); 226 | helper_container.push_back(myInt); 227 | 228 | f.check(); 229 | } 230 | 231 | BOOST_REQUIRE_EQUAL(f.unused_count(), 0); 232 | 233 | // now if we allocate more we should fail gracefully: 234 | HDummyInt myInt = f.allocate_through_init(4); 235 | BOOST_REQUIRE(!myInt); 236 | 237 | // This should hold always: 238 | // m_free_count+m_inuse_count == m_total_count 239 | BOOST_REQUIRE_EQUAL(f.unused_count() + f.inuse_count(), f.capacity()); 240 | BOOST_REQUIRE_EQUAL(f.inuse_count(), testArray[testIdx].initial_size); 241 | BOOST_REQUIRE_EQUAL(f.capacity(), testArray[testIdx].initial_size); 242 | BOOST_REQUIRE(!f.empty()); 243 | BOOST_REQUIRE_EQUAL(f.enlarge_steps_done(), 1); // just the initial one 244 | 245 | helper_container.clear(); 246 | f.clear(); 247 | 248 | f.check(); 249 | 250 | BOOST_REQUIRE_EQUAL(f.inuse_count(), 0); 251 | BOOST_REQUIRE_EQUAL(f.capacity(), 0); 252 | BOOST_REQUIRE(f.empty()); 253 | } 254 | } 255 | 256 | void max_size_memory_pool() 257 | { 258 | BOOST_TEST_MESSAGE("Starting boost_intrusive_pool<> tests when memory pool has max size"); 259 | 260 | struct { 261 | unsigned int initial_size; 262 | unsigned int enlarge_step; 263 | unsigned int max_size; 264 | } testArray[] = { 265 | { 1, 1, 10 }, // force newline 266 | { 10, 2, 21 }, // force newline 267 | { 100000, 1, 100010 }, // force newline 268 | { 128, 4096, 5000 }, // force newline 269 | }; 270 | 271 | for (int testIdx = 0; testIdx < sizeof(testArray) / sizeof(testArray[0]); testIdx++) { 272 | BOOST_TEST_MESSAGE(std::string("Starting test entry #") + std::to_string(testIdx)); 273 | 274 | boost_intrusive_pool f(testArray[testIdx].initial_size /* initial size */, 275 | testArray[testIdx].enlarge_step /* enlarge step */, testArray[testIdx].max_size /* max size */); 276 | std::vector helper_container; 277 | 278 | BOOST_REQUIRE(f.is_limited()); 279 | BOOST_REQUIRE_EQUAL(f.max_size(), testArray[testIdx].max_size); 280 | 281 | for (unsigned int j = 0; j < testArray[testIdx].initial_size; j++) { 282 | HDummyInt myInt = f.allocate_through_init(3); 283 | assert(myInt); 284 | helper_container.push_back(myInt); 285 | 286 | f.check(); 287 | } 288 | 289 | // When initial_size items are allocated, the memory pool will try to pre-allocate enlarge_step items. 290 | // At that point, unused_count will be equal to enlarge_step. 291 | // But if initial_size + enlarge_step exceeds max_size, the enlarge_step will be the remaining to max_size. 292 | size_t enlarge_step = testArray[testIdx].enlarge_step; 293 | if (testArray[testIdx].initial_size + testArray[testIdx].enlarge_step > testArray[testIdx].max_size) 294 | enlarge_step = testArray[testIdx].max_size - testArray[testIdx].initial_size; 295 | BOOST_REQUIRE_EQUAL(f.unused_count(), enlarge_step); 296 | 297 | for (unsigned int j = testArray[testIdx].initial_size; j < testArray[testIdx].max_size; j++) { 298 | HDummyInt myInt = f.allocate_through_init(3); 299 | assert(myInt); 300 | helper_container.push_back(myInt); 301 | 302 | f.check(); 303 | } 304 | 305 | BOOST_REQUIRE_EQUAL(f.unused_count(), 0); 306 | 307 | // now if we allocate more we should fail gracefully: 308 | HDummyInt myInt = f.allocate_through_init(4); 309 | BOOST_REQUIRE(!myInt); 310 | 311 | // This should hold always: 312 | BOOST_REQUIRE_EQUAL(f.unused_count() + f.inuse_count(), f.capacity()); 313 | BOOST_REQUIRE_EQUAL(f.inuse_count(), testArray[testIdx].max_size); 314 | BOOST_REQUIRE_EQUAL(f.capacity(), testArray[testIdx].max_size); 315 | BOOST_REQUIRE(!f.empty()); 316 | BOOST_REQUIRE(f.enlarge_steps_done() > 0); 317 | 318 | helper_container.clear(); 319 | f.clear(); 320 | 321 | f.check(); 322 | 323 | BOOST_REQUIRE_EQUAL(f.inuse_count(), 0); 324 | BOOST_REQUIRE_EQUAL(f.capacity(), 0); 325 | BOOST_REQUIRE(f.empty()); 326 | } 327 | } 328 | 329 | /// Test the basic API construct and free some objects 330 | void test_api() 331 | { 332 | { 333 | uint32_t recycled = 0; 334 | boost_intrusive_pool pool; 335 | 336 | BOOST_REQUIRE(pool.init()); 337 | 338 | auto recycle_fn = [&recycled](dummy_one& object) { 339 | BOOST_REQUIRE(object.m_count > 0); 340 | object.m_count--; 341 | ++recycled; 342 | }; 343 | 344 | pool.set_recycle_method(RECYCLE_METHOD_CUSTOM_FUNCTION, recycle_fn); 345 | 346 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 347 | 348 | { 349 | auto d1 = pool.allocate(); 350 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 1); 351 | } 352 | 353 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 354 | 355 | auto d2 = pool.allocate(); 356 | 357 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 1); 358 | 359 | auto d3 = pool.allocate(); 360 | 361 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 2); 362 | 363 | // the dummy_one::m_count counter is off by 1 because above we called an allocate() and 364 | // then destroyed the "d1" pointer: that resulted in the item being recycled and dummy_one::~dummy_one 365 | // being called 366 | BOOST_REQUIRE_EQUAL(dummy_one::m_count, BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 1); 367 | 368 | { 369 | auto d4 = pool.allocate(); 370 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 3); 371 | } 372 | 373 | BOOST_REQUIRE_EQUAL(pool.unused_count(), BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE - 2); 374 | 375 | // FIXME FIXME THIS DOES NOT WORK CURRENTLY SINCE ALL SMART POINTER DTORS 376 | // ARE STILL ALIVE AT THIS POINT 377 | pool.clear(); 378 | BOOST_REQUIRE_EQUAL(pool.unused_count(), 0U); 379 | } 380 | 381 | // BOOST_REQUIRE(dummy_one::m_count, 0); 382 | } 383 | 384 | void test_allocate_methods() 385 | { 386 | { 387 | boost_intrusive_pool pool; 388 | 389 | BOOST_REQUIRE(pool.init()); 390 | 391 | auto o1 = pool.allocate(); 392 | auto o2 = pool.allocate(); 393 | 394 | // the pool constructs BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE items 395 | // and then we did not call any ctor (using allocate() nothing gets called inside 396 | // the dummy_two class): 397 | BOOST_REQUIRE_EQUAL(dummy_two::m_count, BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 398 | } 399 | 400 | // now all BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE items have been destroyed through 401 | // their dtor so the count returns to zero: 402 | BOOST_REQUIRE_EQUAL(dummy_two::m_count, 0); 403 | 404 | { 405 | boost_intrusive_pool pool; 406 | 407 | BOOST_REQUIRE(pool.init()); 408 | 409 | auto o1 = pool.allocate_through_init(3U); 410 | auto o2 = pool.allocate_through_init(3U); 411 | 412 | // the pool constructs BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE items 413 | BOOST_REQUIRE_EQUAL(dummy_two::m_count, BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 414 | } 415 | 416 | // now all BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE items have been destroyed through 417 | // their dtor so it just remains an offset = 2 in the static instance count: 418 | BOOST_REQUIRE_EQUAL(dummy_two::m_count, 0); 419 | } 420 | 421 | /// Test that everything works even if the pool dies before the 422 | /// objects allocated 423 | void pool_die_before_object() 424 | { 425 | { 426 | boost::intrusive_ptr d1; 427 | boost::intrusive_ptr d2; 428 | boost::intrusive_ptr d3; 429 | 430 | dummy_one::m_count = 0; 431 | 432 | { 433 | boost_intrusive_pool pool; 434 | 435 | BOOST_REQUIRE(pool.init()); 436 | 437 | d1 = pool.allocate(); 438 | d2 = pool.allocate(); 439 | d3 = pool.allocate(); 440 | 441 | BOOST_REQUIRE_EQUAL(dummy_one::m_count, BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 442 | } 443 | 444 | BOOST_REQUIRE_EQUAL(dummy_one::m_count, BOOST_INTRUSIVE_POOL_DEFAULT_POOL_SIZE); 445 | } 446 | 447 | BOOST_REQUIRE_EQUAL(dummy_one::m_count, 0); 448 | } 449 | 450 | void overwrite_pool_items_with_other_pool_items() 451 | { 452 | boost_intrusive_pool pool(64, 16, 0, memorypool::RECYCLE_METHOD_DESTROY_FUNCTION); 453 | 454 | pool.check(); 455 | 456 | { 457 | std::map helper_container; 458 | for (unsigned int j = 0; j < 1000; j++) { 459 | HDummyInt myInt = pool.allocate_through_init(j); 460 | assert(myInt); 461 | 462 | pool.check(); 463 | 464 | // put the item inside a MAP container 465 | helper_container[j] = myInt; 466 | } 467 | 468 | pool.check(); 469 | 470 | BOOST_REQUIRE(!pool.is_memory_exhausted()); 471 | BOOST_REQUIRE_EQUAL(pool.inuse_count(), 1000); 472 | 473 | // now allocate an other block of items and check refcounting happens correctly so that 474 | // if we overwrite a filled map entry, the previous pointer is erased correctly 475 | for (unsigned int j = 0; j < 500; j++) { 476 | HDummyInt myInt = pool.allocate_through_init(j); 477 | assert(myInt); 478 | 479 | pool.check(); 480 | 481 | // put the item inside a MAP container 482 | helper_container[j] = myInt; 483 | } 484 | 485 | pool.check(); 486 | 487 | BOOST_REQUIRE(!pool.is_memory_exhausted()); 488 | BOOST_REQUIRE_EQUAL( 489 | pool.inuse_count(), 1000); // should be still 1000: we allocated 500 more but released other 500 entries! 490 | 491 | helper_container.clear(); // all pointers it contains will be now released 492 | } 493 | 494 | BOOST_REQUIRE_EQUAL(pool.inuse_count(), 0); 495 | 496 | // repeat twice for test: 497 | pool.clear(); 498 | pool.clear(); 499 | 500 | pool.check(); 501 | 502 | BOOST_REQUIRE_EQUAL(pool.inuse_count(), 0); 503 | BOOST_REQUIRE_EQUAL(pool.capacity(), 0); 504 | BOOST_REQUIRE(pool.empty()); 505 | BOOST_REQUIRE_EQUAL(pool.unused_count(), 0); 506 | } 507 | 508 | boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) 509 | { 510 | // about M_PERTURB: 511 | // If this parameter is set to a nonzero value, then bytes of allocated memory 512 | // are scrambled; This can be useful for detecting 513 | // errors where programs incorrectly rely on allocated memory 514 | // being initialized to zero, or reuse values in memory that has 515 | // already been freed. 516 | mallopt(M_PERTURB, 1); 517 | 518 | boost::unit_test::test_suite* test = BOOST_TEST_SUITE("Master test suite"); 519 | 520 | test->add(BOOST_TEST_CASE(&infinite_memory_pool)); 521 | test->add(BOOST_TEST_CASE(&bounded_memory_pool)); 522 | test->add(BOOST_TEST_CASE(&max_size_memory_pool)); 523 | test->add(BOOST_TEST_CASE(&test_api)); 524 | test->add(BOOST_TEST_CASE(&test_allocate_methods)); 525 | test->add(BOOST_TEST_CASE(&pool_die_before_object)); 526 | test->add(BOOST_TEST_CASE(&overwrite_pool_items_with_other_pool_items)); 527 | 528 | return test; 529 | } 530 | --------------------------------------------------------------------------------