所有代码只针对测试数据进行特别优化,可能不具有普适性
代码预览
显式空闲链表+分离适配+realloc_bal测试点优化+binary_bal测试点优化
宏定义
宏定义和书本基本相同,但增加了PRED和SUCC两个宏用作定位空闲块中的前驱指针和后继指针,前驱指针保存在空闲块首起4个字节,而后继指针保存在后4个字节
函数及全局变量声明
root指针数组用作分离适配器的空闲链表数组,维护着链表头
MAX_SIZE为root数组的大小
heap_listp为堆顶指针
根据2的幂划分块的大小:{08},{916},{1732},…,{20494096},{4097~∞}
mm_init函数
相比于书上代码增加了对root的初始化,同时改变初始扩展堆的大小(原为CHUNKSIZE/WSIZE,现改为115字节),经过测试,改后代码能比原代码增加5分
extend_heap函数
和书上代码相同
mm_malloc函数
和书上代码基本相同,但由于place函数由void类型改为void*类型,同时改变形参数量,所以对place函数有关代码做出了调整
mm_free函数
相比于书上代码增加了对于空闲块的前驱指针和后继指针的初始化操作
mm_realloc函数
ptr为空时直接调用mm_malloc函数,size为0时直接调用mm_free函数,否则
首先将输入的size进行8字节对齐,得到asize
接着判断ptr本身大小是否大于asize,如果小于等于则直接返回ptr指针(抛弃空闲区域),否则判断ptr后是否为空闲块且空闲块是最后一块,如果是则判断两者合并后是否放的下,如果放得下则直接更新ptr并返回,否则进行堆扩展并更新返回
如果不满足上述所有情况则进行常规mm_malloc并进行内存复制
judge函数
用于判断size大小的空闲块应该被放置在哪个空闲链表数组中
place函数
flag用于判断调用者来源于malloc还是realloc函数(realloc函数无需进行relink操作)
对于剩余部分大于2*DSIZE的情况,对asize进行判断,如果小于96(实际范围可以在80~110之间浮动,分数相同),则将空闲块放在右边,否则则将空闲块放在左边,放置的同时将空闲块的前驱和后继初始化并插入空闲链表中
对于剩余部分小于2*DSIZE的情况,抛弃剩余空闲区域
返回指针为分配块所对应指针
coalesce函数
和书上代码基本类似,只是在对应情况下加上relink对应空闲块
最后加上updatenode函数更新新的空闲块到对于空闲链表中
find_fit函数
从对应空闲链表数组中开始寻找,首次适配,如果当前数组中没有找到则跳入下一下标中继续寻找,如果所有空闲块都无法放入则返回NULL
relink函数
基础的空闲链表删除节点函数,最后对bp进行初始化
updatenode函数
基础的空闲链表(OrderedList)插入节点函数,链表为升序排列,分头部插入和非头部插入两种情况
优化原理
- 采用显式空闲链表相对于隐式空闲链表,在插入的过程中只需遍历空闲链表而非整个堆,能大大加快速度
- 采用分离适配,将将近大小的块放入一个链表中,类似于链式哈希表,能大大加快检索速度,同时由于优先遍历相近的块,所以相近大小的分配块会被分配到相近大小的空闲块中,提升内存利用率
- 链表采用升序排列,首次适配,所以每次都能适配到最接近大小的空闲块,即最佳适配,能在牺牲一小部分速度的同时大大提升内存利用率
- 针对realloc_bal和realloc2_bal测试点进行优化,注意到测试点中对同一块区域进行反复realloc,size升序排列,同时内部穿插alloc和free,此时若对于realloc的块直接向后扩展堆,同时抛弃空闲块,则不会打乱堆中的顺序,使内存利用率上升
- 针对binary_bal和binary2_bal测试点进行优化,注意到测试点中先反复alloc小块和大块,之后一次性free大块并alloc更大的块,如果不进行优化,则由于之后alloc的块并不能被插入到之前free的块中(大小不够),所以会造成空间的大量浪费,而选择将大块的空闲块放在左边,小块的空闲块放在右边,当free大块时,小块的空闲块,大块的空闲块和大块free的空间都能相合并,形成一个更大的空间并能容纳下之后alloc的块,能大大提升内存利用率
- 其他优化:改变mm_init中初始堆的大小,经测试,当初始堆的大小为115字节时有最大内存利用率(仅对测试点进行优化,无普适性)
实验过程及分析
详细注释请见完整代码
- 使用书本上代码(隐式空闲链表)进行首次尝试
可以发现内存的利用率只有74%,而且速度也很慢 - 随后尝试将隐式改为显式空闲链表(LIFO)
时间方面已经到达满分,之后需要对空间进行进一步优化 - 考虑采用分离适配方法(LIFO),将块大小分解到131072大小
内存利用率只有2%的提升,这也在意料之中,因为分离适配主要提升速度
首先观察到最后4个测试点的内存利用率非常低,首先考虑优化最后两个测试点(realloc_bal和realloc2_bal) - 通过观察测试点后选择修改realloc函数如下
即首先判断asize是否和copySize相等,如果相等则直接返回ptr,如果小于则调用place函数对分配块进行重新分配,否则则调用coalesce2函数(和coalesce函数类似),对于原块的前后寻找是否存在空闲块,如果存在则对其进行合并并重新分配,否则则重新alloc并内存复制 - 此外修改updatenode函数代码,使其变为升序排列,这样就可以配合哈希表达到不损耗太多速度的同时进行最优适配
此时内存利用率有了部分的提升 - 接着考虑对后部空闲块直接扩展堆,即判断
1 | (( !GET_ALLOC(HDRP(NEXT_BLKP(ptr)))&&!GET_SIZE(HDRP(NEXT_BLKP(NEXT_BLKP(ptr)))))) |
- 在修改完代码并确认代码无误后却一直发生段错误
在尝试了许多办法后发现将CHUNKSIZE的大小缩减到256字节后就不再发生段错误,推测可能是代码之间仍然存在逻辑错误,或是由于测试点的特殊性导致段错误,而将CHUNKSIZE进行了修改后正好改变了分配块在内存中的排布顺序,从而消除了段错误 - 同时最后的测试点有了较大幅度的提升,已经到 97%
随后尝试对binary_bal和binary2_bal测试点进行优化
考虑按照块的大小选择place分配方式,即大块分在右边,而小块分在左边,分界线介于80~110字节之间 - 但在修改完代码后发现
所有测试点均在第六行发生负载覆盖问题,通过查看测试点可以知道第一次malloc可以正常执行,但在进行第二次malloc后就覆盖掉了第一次malloc的内容 - 调试发现
发现第一次将分配块(0xf697c118)的ALLOC置为1,但在 第二次malloc时ALLOC被隐式地修改回了0
经过长时间的调试发现问题在于没有修改传入的bp指针,导致bp指针指向了错误的位置,而在之后malloc中影响到了之前的ALLOC
所以将place函数从void类型修改为void *类型,将修改后的bp指针传出,并在malloc函数中更新bp指针即可消除负载覆盖的问题 - 但这时又再次出现了段错误的问题
经过排查发现是coalesce2函数中判断块的前部是否存在空闲块和place函数发生了冲突
简单起见,直接暴力删除coalesce2中对于flag==2的情况,同时将CHUNKSIZE修改回4096 - 修改后最后两个测试点发生error
- 考虑coealesce2函数和其他许多函数可能发生了冲突,最后选择直接删除coalesce2整个函数
分数提升到了92分
最后通过重整代码和修改CHUNSIZE大小和初始堆的大小,成功再次提升5分 - 最终实验结果
完整代码
1 | /* |