├── .gitignore ├── README.md ├── allocator.hpp ├── naive_allocator.hpp ├── resource └── Fig1.png └── tester.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Others 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STL-allocator 2 | 3 | ## STL Allocator Interface 4 | 5 | An allocator is used by standard library containers as a template parameter : 6 | 7 | ```c++ 8 | template < class T, class Alloc = allocator > class vector; 9 | template < class T, class Alloc = allocator > class list; 10 | ``` 11 | 12 | What does an ``allocator`` class have? Typically, it possesses: 13 | 14 | ```c++ 15 | typedef void _Not_user_specialized; 16 | typedef _Ty value_type; 17 | typedef value_type *pointer; 18 | typedef const value_type *const_pointer; 19 | typedef value_type& reference; 20 | typedef const value_type& const_reference; 21 | typedef size_t size_type; 22 | typedef ptrdiff_t difference_type; 23 | typedef true_type propagate_on_container_move_assignment; 24 | typedef true_type is_always_equal; 25 | 26 | pointer address(reference _Val) const _NOEXCEPT 27 | const_pointer address(const_reference _Val) const _NOEXCEPT 28 | void deallocate(pointer _Ptr, size_type _Count) 29 | _DECLSPEC_ALLOCATOR pointer allocate(size_type _Count) 30 | template void destroy(_Uty *_Ptr) 31 | template 32 | void construct(_Objty *_Ptr, _Types&&... _Args) 33 | ``` 34 | 35 | The above interface is just shown for illustration, please refer to [std::allocator](https://en.cppreference.com/w/cpp/memory/allocator) for the latest specification. 36 | 37 | ## Memory Pool 38 | 39 | STL provides you a default [std::allocator](https://en.cppreference.com/w/cpp/memory/allocator), but you can implement your own to replace it. For example, you can design a memory pool to speed up the dynamic allocation of a large number of small blocks (e.g., 8 bytes, 16 bytes, ...), and to reduce memory fragmentation. 40 | 41 | ![Fig1](https://github.com/ZSYTY/STL-allocator/blob/master/resource/Fig1.png) 42 | 43 | Figure 1: Mem pool using block based allocation strategy. 44 | 45 | ## Requirements 46 | 47 | - Two people as a group to finish this project (don't forget to write down the group member names, anyone of the - two can submit the final package on PTA). 48 | - Implement your own memory allocator for STL vector. 49 | - The allocator should optimize the memory allocation speed using memory pool. 50 | - The allocator should support arbitrary memory size allocation request. 51 | 52 | ## How to Test Your Allocator 53 | 54 | Basically, you should: 55 | 56 | 1. Create more than ten thousand vectors with different number of elements. 57 | 2. Pick up 1000 random vectors and resize the vectors with random sizes. 58 | 3. Release all the vectors. 59 | 60 | Feel free to extend the following code skeleton for your own tests: 61 | 62 | ```c++ 63 | #include 64 | #include 65 | #include 66 | 67 | // include header of your allocator here 68 | template 69 | using MyAllocator = std::allocator; // replace the std::allocator with your allocator 70 | using Point2D = std::pair; 71 | 72 | const int TestSize = 10000; 73 | const int PickSize = 1000; 74 | 75 | int main() 76 | { 77 | std::random_device rd; 78 | std::mt19937 gen(rd()); 79 | std::uniform_int_distribution<> dis(1, TestSize); 80 | 81 | // vector creation 82 | using IntVec = std::vector>; 83 | std::vector> vecints(TestSize); 84 | for (int i = 0; i < TestSize; i++) 85 | vecints[i].resize(dis(gen)); 86 | 87 | using PointVec = std::vector>; 88 | std::vector> vecpts(TestSize); 89 | for (int i = 0; i < TestSize; i++) 90 | vecpts[i].resize(dis(gen)); 91 | 92 | // vector resize 93 | for (int i = 0; i < PickSize; i++) 94 | { 95 | int idx = dis(gen) - 1; 96 | int size = dis(gen); 97 | vecints[idx].resize(size); 98 | vecpts[idx].resize(size); 99 | } 100 | 101 | // vector element assignment 102 | { 103 | int val = 10; 104 | int idx1 = dis(gen) - 1; 105 | int idx2 = vecints[idx1].size() / 2; 106 | vecints[idx1][idx2] = val; 107 | if (vecints[idx1][idx2] == val) 108 | std::cout << "correct assignment in vecints: " << idx1 << std::endl; 109 | else 110 | std::cout << "incorrect assignment in vecints: " << idx1 << std::endl; 111 | } 112 | { 113 | Point2D val(11, 15); 114 | int idx1 = dis(gen) - 1; 115 | int idx2 = vecpts[idx1].size() / 2; 116 | vecpts[idx1][idx2] = val; 117 | if (vecpts[idx1][idx2] == val) 118 | std::cout << "correct assignment in vecpts: " << idx1 << std::endl; 119 | else 120 | std::cout << "incorrect assignment in vecpts: " << idx1 << std::endl; 121 | } 122 | 123 | return 0; 124 | } 125 | ``` 126 | ## Evaluation Standard 127 | 128 | 1. c++ code quality (clean, compact and reasonable) 129 | 2. comments quality 130 | 3. correctness and running performance of the allocator 131 | 132 | ## Files to Submit 133 | 134 | Please prepare a .zip package including the following items: 135 | 136 | 1. the source code (including the testing code) 137 | 2. makefile (for Mac or Linux users) or .exes (for Windows users, with necessary .dlls if you use MinGW) or CMakeLists.txt 138 | -------------------------------------------------------------------------------- /allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALLOCATOR_HPP 2 | #define ALLOCATOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // malloc-based allocator 11 | class __fir_alloc { 12 | public: 13 | static void *allocate(std::size_t n) { 14 | void *res = std::malloc(n); 15 | if (res == nullptr) throw std::bad_alloc(); 16 | return res; 17 | } 18 | 19 | static void deallocate(void *p, std::size_t) { std::free(p); } 20 | }; 21 | 22 | // the max size of small block in memory pool 23 | enum { MAX_BYTES = 65536 }; 24 | 25 | // the align size of each block, i.e. the size could be divided by 64 26 | enum { BOUND = 64 }; 27 | 28 | // the number of free lists 29 | enum { COUNT_FREE_LISTS = MAX_BYTES / BOUND }; 30 | 31 | // the union object of free list 32 | union obj { 33 | union obj *free_list_link; 34 | char client_data[1]; 35 | }; 36 | 37 | // the second allocator using memory pool 38 | class __sec_alloc { 39 | private: 40 | // free_lists 41 | static obj *volatile free_list[COUNT_FREE_LISTS]; 42 | 43 | // chunk allocation state 44 | static char *start_mem_pool; 45 | static char *end_mem_pool; 46 | static std::size_t heap_size; 47 | 48 | // determine the index of free list 49 | static std::size_t free_list_idx(std::size_t bytes) { 50 | return ((bytes) + BOUND - 1) / BOUND - 1; 51 | } 52 | 53 | // align the block 54 | static std::size_t ceil_up(std::size_t bytes) { 55 | return (((bytes) + BOUND - 1) & ~(BOUND - 1)); 56 | } 57 | 58 | // allocate using the memory pool 59 | static char *chunk_alloc(std::size_t size, int &cnt_obj) { 60 | char *res; 61 | std::size_t total_bytes = size * cnt_obj; 62 | // the space left in the memory pool 63 | std::size_t bytes_left = end_mem_pool - start_mem_pool; 64 | 65 | if (bytes_left >= total_bytes) { 66 | // if the space left is enough 67 | res = start_mem_pool; 68 | start_mem_pool += total_bytes; 69 | return res; 70 | } else if (bytes_left >= size) { 71 | // the space is not enough for requirement 72 | // but enough for at least one block 73 | cnt_obj = bytes_left / size; 74 | total_bytes = size * cnt_obj; 75 | res = start_mem_pool; 76 | start_mem_pool += total_bytes; 77 | return res; 78 | } else { 79 | // the space is even not enough for one block 80 | std::size_t bytes_to_get = 81 | 2 * total_bytes + ceil_up(heap_size >> 4); 82 | // try to make use of the scattered space 83 | if (bytes_to_get > 0 and start_mem_pool != nullptr) { 84 | // if there is still some space 85 | obj *volatile *my_free_list = 86 | free_list + free_list_idx(bytes_left); 87 | // adjust the free list 88 | ((obj *)start_mem_pool)->free_list_link = *my_free_list; 89 | *my_free_list = (obj *)start_mem_pool; 90 | } 91 | 92 | // supply the memory pool 93 | start_mem_pool = (char *)malloc(bytes_to_get); 94 | heap_size += bytes_to_get; 95 | end_mem_pool = start_mem_pool + bytes_to_get; 96 | 97 | // adjust the cnt_obj 98 | return chunk_alloc(size, cnt_obj); 99 | } 100 | } 101 | 102 | // return an object consuming the space of n 103 | static void *refill(std::size_t n) { 104 | int cnt_obj = 20; 105 | // cnt_obj is passed by reference 106 | char *chunk = chunk_alloc(n, cnt_obj); 107 | obj *volatile *my_free_list; 108 | obj *res; 109 | obj *current_obj, *next_obj; 110 | 111 | // if get only one block, return it 112 | if (cnt_obj == 1) return chunk; 113 | // prepare to adjust free list 114 | my_free_list = free_list + free_list_idx(n); 115 | // the block to be returned 116 | res = (obj *)chunk; 117 | *my_free_list = next_obj = (obj *)(chunk + n); 118 | 119 | // link the remaining block 120 | for (int i = 1;; i++) { 121 | current_obj = next_obj; 122 | next_obj = (obj *)((char *)next_obj + n); 123 | if (cnt_obj - 1 == i) { 124 | current_obj->free_list_link = nullptr; 125 | break; 126 | } else { 127 | current_obj->free_list_link = next_obj; 128 | } 129 | } 130 | return res; 131 | } 132 | 133 | public: 134 | static void *allocate(std::size_t n) { 135 | obj *volatile *cur_free_list; 136 | obj *res; 137 | 138 | // if n is large enough call the first allocator 139 | if (n > (std::size_t)MAX_BYTES) return __fir_alloc::allocate(n); 140 | 141 | // find the suitable free list 142 | cur_free_list = free_list + free_list_idx(n); 143 | res = *cur_free_list; 144 | 145 | // refill the free list if there is no free list available 146 | if (res == nullptr) { 147 | void *r = refill(ceil_up(n)); 148 | return r; 149 | } 150 | 151 | // adjust the free list 152 | *cur_free_list = res->free_list_link; 153 | return res; 154 | } 155 | 156 | static void deallocate(void *p, std::size_t n) { 157 | obj *q = (obj *)p; 158 | obj *volatile *cur_free_list; 159 | 160 | // if n is large enough, call the first deallocator 161 | if (n > (std::size_t)MAX_BYTES) { 162 | __fir_alloc::deallocate(p, n); 163 | return; 164 | } 165 | 166 | // find the suitable free list 167 | cur_free_list = free_list + free_list_idx(n); 168 | 169 | // adjust the free list 170 | q->free_list_link = *cur_free_list; 171 | *cur_free_list = q; 172 | } 173 | }; 174 | 175 | // initialize the second allocator 176 | char *__sec_alloc ::start_mem_pool = nullptr; 177 | 178 | char *__sec_alloc ::end_mem_pool = nullptr; 179 | 180 | std::size_t __sec_alloc::heap_size = 0; 181 | 182 | obj *volatile __sec_alloc::free_list[COUNT_FREE_LISTS] = {nullptr}; 183 | 184 | // the interface of allocator 185 | template 186 | class Mallocator { 187 | public: 188 | typedef void _Not_user_specialized; 189 | typedef T value_type; 190 | typedef value_type *pointer; 191 | typedef const value_type *const_pointer; 192 | typedef value_type &reference; 193 | typedef const value_type &const_reference; 194 | typedef std::size_t size_type; 195 | typedef ptrdiff_t difference_type; 196 | typedef std::true_type propagate_on_container_move_assignment; 197 | typedef std::true_type is_always_equal; 198 | 199 | Mallocator() = default; 200 | 201 | template 202 | Mallocator(const Mallocator &other) noexcept; 203 | 204 | pointer address(reference _Val) const noexcept { return &_Val; } 205 | 206 | const_pointer address(const_reference _Val) const noexcept { return &_Val; } 207 | 208 | pointer allocate(size_type _Count) { 209 | if (_Count > size_type(-1) / sizeof(value_type)) throw std::bad_alloc(); 210 | if (auto p = static_cast( 211 | __sec_alloc::allocate(_Count * sizeof(value_type)))) 212 | return p; 213 | throw std::bad_alloc(); 214 | } 215 | 216 | void deallocate(pointer _Ptr, size_type _Count) { 217 | __sec_alloc::deallocate(_Ptr, _Count); 218 | } 219 | 220 | template 221 | void destroy(_Uty *_Ptr) { 222 | _Ptr->~_Uty(); 223 | } 224 | 225 | template 226 | void construct(_Objty *_Ptr, _Types &&... _Args) { 227 | ::new (const_cast(static_cast(_Ptr))) 228 | _Objty(std::forward<_Types>(_Args)...); 229 | } 230 | }; 231 | 232 | template 233 | bool operator==(const Mallocator &lhs, const Mallocator &rhs) { 234 | return true; 235 | } 236 | 237 | template 238 | bool operator!=(const Mallocator &lhs, const Mallocator &rhs) { 239 | return false; 240 | } 241 | 242 | #endif 243 | -------------------------------------------------------------------------------- /naive_allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NAIVE_ALLOCATOR_HPP 2 | #define NAIVE_ALLOCATOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // naive allocator interface 11 | // using malloc and free only 12 | template 13 | class Nallocator { 14 | public: 15 | typedef T value_type; 16 | typedef size_t size_type; 17 | typedef ptrdiff_t difference_type; 18 | 19 | Nallocator() = default; 20 | 21 | template 22 | Nallocator(const Nallocator& other) noexcept; 23 | 24 | T* allocate(std::size_t n) { 25 | auto buf = (T*)(::operator new((std::size_t)(n * sizeof(T)))); 26 | if (buf == 0) throw std::bad_alloc(); 27 | return buf; 28 | } 29 | 30 | void deallocate(T* buf, std::size_t) { ::operator delete(buf); } 31 | }; 32 | 33 | template 34 | bool operator==(const Nallocator& lhs, const Nallocator& rhs) { 35 | return true; 36 | } 37 | 38 | template 39 | bool operator!=(const Nallocator& lhs, const Nallocator& rhs) { 40 | return false; 41 | } 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /resource/Fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSYTY/STL-allocator/a7281ab430e960f38009a336d4489fe6d2f28d97/resource/Fig1.png -------------------------------------------------------------------------------- /tester.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "allocator.hpp" 7 | #include "naive_allocator.hpp" 8 | 9 | using Point2D = std::pair; 10 | 11 | const int TestSize = 10000; 12 | // PickSize is increase to 10000 to make the test result more apparent 13 | const int PickSize = 10000; 14 | 15 | // The tester for different allocator 16 | template