## 关于示例代码的理解 #### 环境 * 从 `mem_init` 中可以看出整个分配器是在一大段已经分配好的连续的内存中进行的 * 同样每次 `mem_sbrk` 进行扩展时也是连续扩展,模拟堆的向上增长 #### 对齐 * 标准 `malloc` 也是八字节对齐,所以可以满足强制八字节对齐的要求 * 返回的内存应为有效载荷部分,如果每个块加上头部和脚部,为了满足对齐要求,须在开头空出四字节的位置 * 也意味着空闲块最小为八字节大小,有效载荷也应八字节对齐,已分配块最小十六字节 #### 测试 * handout 里的测试用例偏少,从网上找到了更为详尽的 `traces` 测试用例来测试 ## version 1 #### 规则与注意 理解逻辑后,接下来将实现自己的版本 * 为方便理解,定义了两个类型别名 ```c typedef unsigned int word; typedef char byte; ``` * 不同于示例代码用序言块和尾块标记,本人仅用两个指针标记头尾,来提高内存利用率 ```c // mark the front and tail pos void *front_p = NULL; void *tail_p = NULL; ``` * 但会增加代码的复杂度,需着重维护 * 同时还应注意若 `bp == front_p` 则 `PREV(bp)` 内的值无效,`tail_p` 同理 * 为保持一致性,向辅助函数内传入的 `size` 均应在传入之前对齐,均不包含头尾部大小 * 仅在内部碎片大于等于十六字节时才进行切割 * 其他部分与示例大同小异 #### bug 与 debug * `#debug`对于 `segmentation fault` 使用 `gdb` 获取头尾块的 `size` 发现尾部异常值 `0xcdcdcd`,在代码中使用 `print` 跟踪 `trail_p` 变量,发现在`__coalesce_next`处没有及时更新 * `#bug1` 若记录的 `size` 是有效载荷的 `size`,合并和分割时应注意增减 `DSIZE` * `#bug2` 每次合并都需要判断 `tail_p` 是否改变,特别是 `__coalesce_next` 的情况 #### 方法与得分 * 隐式空闲链表,首次适配,立即合并 ```c Results for mm malloc: trace valid util ops secs Kops 0 yes 99% 5694 0.007579 751 1 yes 100% 5848 0.006639 881 2 yes 99% 6648 0.010560 630 3 yes 100% 5380 0.008016 671 4 yes 100% 14400 0.000102140762 5 yes 92% 4800 0.006677 719 6 yes 92% 4800 0.005988 802 7 yes 55% 12000 0.141468 85 8 yes 51% 24000 0.274197 88 9 yes 33% 14401 0.128358 112 10 yes 50% 14401 0.002138 6734 Total 79% 112372 0.591722 190 Perf index = 47 (util) + 13 (thru) = 60/100 ``` ## Version 1.1 #### 针对 `realloc` 的优化 v1 * 若 `new_size <= old_size` 则不分配而是切割 * 若下一块未分配且总和大于 `new_size` 则合并 * 若合并后内部碎片过大则仍需分割 * 提高六分,但判断过多,且考虑不全 #### 针对 `realloc` 的优化 v2 * 评估前后空闲块的总大小,若足够,则合并 * 合并不会破坏数据,合并后复制数据,再根据需要分割 * 然而针对第九项测试,合并前后空闲块反而内存利用率低于仅合并后空闲块 #### 针对 `realloc` 的优化 v3 * 最终选择的是逐步的过程,因为从时间开销上来看,直接返回优于仅合并后部分优于合并前后部分,但同时合并前后部分与再分配一段内存的优劣不好比较 * 现在的问题是在仅合并后部分和重新分配之间要不要插一段合并前后部分的条件,两者分数相同,个人认为插入这个条件通用性更好 #### 得分 ```c Results for mm malloc: trace valid util ops secs Kops 0 yes 99% 5694 0.007401 769 1 yes 100% 5848 0.006883 850 2 yes 99% 6648 0.011138 597 3 yes 100% 5380 0.008327 646 4 yes 100% 14400 0.000092156013 5 yes 92% 4800 0.006244 769 6 yes 92% 4800 0.005888 815 7 yes 55% 12000 0.142196 84 8 yes 51% 24000 0.277304 87 9 yes 50% 14401 0.018129 794 10 yes 86% 14401 0.000132108933 Total 84% 112372 0.483734 232 Perf index = 50 (util) + 15 (thru) = 66/100 ``` ## Version 1.2 #### 使用 `next fit` * 因为大块的空闲块总是趋向于在后面,所以下一次适配不用从头遍历,可以大幅提高吞吐率 * 引入新的全局变量 `fitted_p` 并且需要在多处维护:初始化,分配,合并 #### 结合 `best fit` * `next_fit` 会降低内存利用率,而 `best_fit` 会降低吞吐率,但我们可以进行一个折衷,即在 `fitted_p` 后部分首次适配,而在 `fitted_p` 前部分最佳适配 * 而最佳适配仅需找到满足要求的最小值即可 * 然而因为前面的内存碎片太多,测试下来内存利用率的确有所提高,但是吞吐率下降的却更多,所以最后还是选择了 `_next_fit` #### 得分 ```c Results for mm malloc: trace valid util ops secs Kops 0 yes 91% 5694 0.001803 3158 1 yes 92% 5848 0.001315 4446 2 yes 97% 6648 0.003706 1794 3 yes 97% 5380 0.003602 1494 4 yes 100% 14400 0.000085169213 5 yes 91% 4800 0.004207 1141 6 yes 90% 4800 0.003837 1251 7 yes 55% 12000 0.057487 209 8 yes 51% 24000 0.029497 814 9 yes 50% 14401 0.054370 265 10 yes 70% 14401 0.000116124684 Total 80% 112372 0.160025 702 Perf index = 48 (util) + 40 (thru) = 88/100 ``` *** 2022.12.29 ~ 2022.12.30