|
21 | 21 |
|
22 | 22 |
|
23 | 23 |
|
| 24 | +#### 内存分配概述 |
| 25 | + |
| 26 | +内存分配器需要实现的功能: |
| 27 | + |
| 28 | +- init (addr, size):初始化内存分配器,向内存分配器提供一段[addr,addr+size)的内存空间; |
| 29 | + |
| 30 | +- add_memory (addr, size):向内存分配器额外增加一段[addr,addr+size)的内存空间; |
| 31 | + |
| 32 | +- alloc (size, align):申请一段空间,大小为size,地址对齐要求为align(为2的幂,一般为8字节),返回分配空间的起始地址addr; |
| 33 | + |
| 34 | +- dealloc (addr, size, align):释放一段先前申请的空间,起始地址为addr,大小为size,地址对齐要求为align。 |
| 35 | + |
| 36 | +评价内存分配算法的指标: |
| 37 | + |
| 38 | +- 性能:通常要求内存分配器单次O(1);不仅取决于性能分配器的效率本身,还有分配内存的连续性等各种因素; |
| 39 | + |
| 40 | +- 空间利用率:尽可能高,内部碎块与外部碎块尽可能少; |
| 41 | + |
| 42 | +- 线程安全等。 |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +#### 算法介绍 |
| 47 | + |
| 48 | +##### (1)basic allocator |
| 49 | + |
| 50 | +将全局所有的空闲块用一个链表来维护 |
| 51 | + |
| 52 | +- Alloc时,查找一个合适的空闲块,切割为合适大小后分配 |
| 53 | + |
| 54 | +- Free时,将其与前后的空闲块合并后插入回链表中 |
| 55 | + |
| 56 | +- 根据查找空闲块的策略不同,分为first fit、best fit、worst fit三种 |
| 57 | + |
| 58 | + - first fit:选取第一个大小足够的内存块 |
| 59 | + - best fit:选取大小足够的内存块中最小的 |
| 60 | + |
| 61 | + - worst fit:选取大小足够的内存块中最大的 |
| 62 | + |
| 63 | +优点:实现相对容易;消除了内碎块 |
| 64 | + |
| 65 | +缺点:可能存在外碎块;查找空闲块时最坏需要遍历整个链表,效率低 |
| 66 | + |
| 67 | +上述代码约600行,于第8周完成。 |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | +##### (2)TLSF |
| 72 | + |
| 73 | +用两级链表来维护大小在一定范围内的内存块 |
| 74 | + |
| 75 | +O(1)的malloc与dealloc; |
| 76 | + |
| 77 | +每次分配的额外空间开销仅为8字节; |
| 78 | + |
| 79 | +内存碎片较少; |
| 80 | + |
| 81 | +支持动态添加和删除内存池; |
| 82 | + |
| 83 | +详细介绍见文档 `tlsf_note.md`。 |
| 84 | + |
| 85 | +优点:单次操作复杂度严格O(1);内碎块相对Buddy和slab更少 |
| 86 | + |
| 87 | +- 取决于二级链表的大小,如取5位则内碎块不超过1/64 |
| 88 | + |
| 89 | +缺点: |
| 90 | + |
| 91 | +- 每次操作时,拆分和合并内存块的开销相对较大; |
| 92 | + |
| 93 | +- 多次申请空间很可能不连续; |
| 94 | + |
| 95 | +- 最小分配单位为16字节,分配小内存块时冗余较大 |
| 96 | + |
| 97 | +分别使用Rust语言实现和接入C语言的既有实现,Rust代码量约1000行,于第11周完成。 |
| 98 | + |
| 99 | + |
| 100 | + |
| 101 | +##### (3)mimalloc |
| 102 | + |
| 103 | +原始算法是保证线程安全的:即可以支持多线程同时申请/释放,无需上锁(Mutex),仅需原子操作(Atomic) |
| 104 | + |
| 105 | +但算法相对较复杂(原版C代码~3500行),目前实现的Rust版本为单线程的简化版 |
| 106 | + |
| 107 | +在mimalloc内存分配器中,内存维护单元分为:堆(Heap)、段(Segment)、页(Page)、块(Block) |
| 108 | + |
| 109 | +每个Page中的块大小都是相同的 |
| 110 | + |
| 111 | +Segment以4MB对齐,承载各种Page |
| 112 | + |
| 113 | +每个线程用一个Heap作为mimalloc的控制结构,核心为维护不同block大小的Page链表 |
| 114 | + |
| 115 | +详细介绍见文档 `mimalloc_note.md`。 |
| 116 | + |
| 117 | +优点:单次操作O(1);连续分配时内存地址大致连续;速度较现在通用的内存分配器快约7%~14%;内碎块相对较小; |
| 118 | + |
| 119 | +缺点: |
| 120 | + |
| 121 | +- 以段(4MB)和页(64KB)为单位维护内存,在分配请求不规则时可能有较大冗余; |
| 122 | + |
| 123 | +- 算法较为复杂。 |
| 124 | + |
| 125 | +当前的Rust语言单线程版本,代码量约1000行,于第15周完成。 |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | +#### APP测试 |
| 130 | + |
| 131 | +主要用于测试内存分配的两个app为 `apps/memtest` 和 `apps/c/memtest` |
| 132 | + |
| 133 | + |
| 134 | + |
24 | 135 | #### 用户态测试
|
25 | 136 |
|
| 137 | +在 `crates/allocator/tests` 中搭建了专用于用户态测试的global_allocator和测试框架 |
| 138 | + |
| 139 | +- global_allocator接入了上述各种内存分配器,以及rust自带的System分配器(linux中,使用的是__libc_malloc) |
| 140 | + |
| 141 | +- 通过init_heap函数接入一个全局static的大数组(512MB)用于内存分配 |
| 142 | + |
| 143 | +- 通过各种分配器对应的init函数来动态切换使用的分配器 |
| 144 | + |
| 145 | +- 实现了各种测试用例的接口以及各种分配器的测试入口,集成为一个test |
| 146 | + |
| 147 | +- 多线程支持:在每个内存分配器外面套一层mutex |
| 148 | + |
| 149 | +新建了`crates/allocator_test`,内部集成了各种测试用例,可以在运行用户态测试时调用 |
| 150 | + |
| 151 | +各个测试用例的介绍: |
| 152 | + |
| 153 | +##### (1) Basic |
| 154 | + |
| 155 | +- Rust语言 |
| 156 | + |
| 157 | +- 与apps/memtest类似 |
| 158 | + |
| 159 | +- 主要为大量Vec和btreemap测试 |
| 160 | + |
| 161 | +##### (2) Align_test |
| 162 | + |
| 163 | +- Rust语言,自行编写 |
| 164 | + |
| 165 | +- 测试align为非8字节的情况 |
| 166 | + |
| 167 | +- 仅有System、TLSF(C和Rust)、mimalloc支持该功能 |
| 168 | + |
| 169 | +##### (3) Multi_thread_test |
| 170 | + |
| 171 | +- Rust语言,自行编写 |
| 172 | + |
| 173 | +- 测试多线程情况下的内存分配效率 |
| 174 | + |
| 175 | +##### (4) Mitest |
| 176 | + |
| 177 | +- C语言 |
| 178 | + |
| 179 | +- Mimalloc仓库中自带的测试用例 |
| 180 | + |
| 181 | +- 仓库地址:https://github.com/microsoft/mimalloc |
| 182 | + |
| 183 | +- 主要测试算法的正确性 |
| 184 | + |
| 185 | +##### (5) Glibc_bench |
| 186 | + |
| 187 | +- C语言 |
| 188 | + |
| 189 | +- 仓库地址:https://github.com/daanx/mimalloc-bench |
| 190 | + |
| 191 | +- 位于`bench/glibc-bench` |
| 192 | + |
| 193 | +- 一系列比较复杂的内存操作,测试各种算法的性能 |
| 194 | + |
| 195 | +##### (6) Malloc_large |
| 196 | + |
| 197 | +- C语言 |
| 198 | + |
| 199 | +- 仓库地址:https://github.com/daanx/mimalloc-bench |
| 200 | + |
| 201 | +- 位于`bench/malloc-large` |
| 202 | + |
| 203 | +- 用于测试对大内存(2~15MB)的申请与释放 |
| 204 | + |
| 205 | +##### (7) Multi_thread_c_test |
| 206 | + |
| 207 | +- C语言,自行编写 |
| 208 | + |
| 209 | +- 功能大致类似于multi_thread_test |
| 210 | + |
| 211 | +- 测试C语言下的多线程内存分配 |
| 212 | + |
| 213 | + |
| 214 | + |
| 215 | +#### 运行用户态测试 |
| 216 | + |
26 | 217 | ```
|
27 | 218 | cargo test -p allocator --release -- --nocapture
|
28 | 219 | ```
|
|
0 commit comments