所有代码只针对测试数据进行特别优化,可能不具有普适性

# 代码预览

显式空闲链表 + 分离适配 + realloc_bal 测试点优化 + binary_bal 测试点优化

# 宏定义

0
宏定义和书本基本相同,但增加了 PRED 和 SUCC 两个宏用作定位空闲块中的前驱指针和后继指针,前驱指针保存在空闲块首起 4 个字节,而后继指针保存在后 4 个字节

# 函数及全局变量声明

1
root 指针数组用作分离适配器的空闲链表数组,维护着链表头
MAX_SIZE 为 root 数组的大小
heap_listp 为堆顶指针
根据 2 的幂划分块的大小:{08},{916},{1732},...,{20494096},

# mm_init 函数

2
相比于书上代码增加了对 root 的初始化,同时改变初始扩展堆的大小(原为 CHUNKSIZE/WSIZE,现改为 115 字节),经过测试,改后代码能比原代码增加 5 分

# extend_heap 函数

3
和书上代码相同

# mm_malloc 函数

4
和书上代码基本相同,但由于 place 函数由 void 类型改为 void * 类型,同时改变形参数量,所以对 place 函数有关代码做出了调整

# mm_free 函数

5
相比于书上代码增加了对于空闲块的前驱指针和后继指针的初始化操作

# mm_realloc 函数

6
ptr 为空时直接调用 mm_malloc 函数,size 为 0 时直接调用 mm_free 函数,否则
首先将输入的 size 进行 8 字节对齐,得到 asize
接着判断 ptr 本身大小是否大于 asize,如果小于等于则直接返回 ptr 指针(抛弃空闲区域),否则判断 ptr 后是否为空闲块且空闲块是最后一块,如果是则判断两者合并后是否放的下,如果放得下则直接更新 ptr 并返回,否则进行堆扩展并更新返回
如果不满足上述所有情况则进行常规 mm_malloc 并进行内存复制

# judge 函数

7
用于判断 size 大小的空闲块应该被放置在哪个空闲链表数组中

# place 函数

8
flag 用于判断调用者来源于 malloc 还是 realloc 函数(realloc 函数无需进行 relink 操作)
对于剩余部分大于 2*DSIZE 的情况,对 asize 进行判断,如果小于 96(实际范围可以在 80~110 之间浮动,分数相同),则将空闲块放在右边,否则则将空闲块放在左边,放置的同时将空闲块的前驱和后继初始化并插入空闲链表中
对于剩余部分小于 2*DSIZE 的情况,抛弃剩余空闲区域
返回指针为分配块所对应指针

# coalesce 函数

9
和书上代码基本类似,只是在对应情况下加上 relink 对应空闲块
最后加上 updatenode 函数更新新的空闲块到对于空闲链表中

# find_fit 函数

10
从对应空闲链表数组中开始寻找,首次适配,如果当前数组中没有找到则跳入下一下标中继续寻找,如果所有空闲块都无法放入则返回 NULL

# relink 函数

11
基础的空闲链表删除节点函数,最后对 bp 进行初始化

# updatenode 函数

12
基础的空闲链表(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 字节时有最大内存利用率(仅对测试点进行优化,无普适性

# 实验过程及分析

详细注释请见完整代码

  • 使用书本上代码(隐式空闲链表)进行首次尝试
    13
    可以发现内存的利用率只有 74%,而且速度也很慢
  • 随后尝试将隐式改为显式空闲链表(LIFO)
    14
    时间方面已经到达满分,之后需要对空间进行进一步优化
  • 考虑采用分离适配方法(LIFO),将块大小分解到 131072 大小
    15
    内存利用率只有 2% 的提升,这也在意料之中,因为分离适配主要提升速度
    首先观察到最后 4 个测试点的内存利用率非常低,首先考虑优化最后两个测试点(realloc_bal 和 realloc2_bal)
  • 通过观察测试点后选择修改 realloc 函数如下
    25
    26
    即首先判断 asize 是否和 copySize 相等,如果相等则直接返回 ptr,如果小于则调用 place 函数对分配块进行重新分配,否则则调用 coalesce2 函数(和 coalesce 函数类似),对于原块的前后寻找是否存在空闲块,如果存在则对其进行合并并重新分配,否则则重新 alloc 并内存复制
  • 此外修改 updatenode 函数代码,使其变为升序排列,这样就可以配合哈希表达到不损耗太多速度的同时进行最优适配
    16
    此时内存利用率有了部分的提升
  • 接着考虑对后部空闲块直接扩展堆,即判断
(( !GET_ALLOC(HDRP(NEXT_BLKP(ptr)))&&!GET_SIZE(HDRP(NEXT_BLKP(NEXT_BLKP(ptr))))))
  • 在修改完代码并确认代码无误后却一直发生段错误
    17
    在尝试了许多办法后发现将 CHUNKSIZE 的大小缩减到 256 字节后就不再发生段错误,推测可能是代码之间仍然存在逻辑错误,或是由于测试点的特殊性导致段错误,而将 CHUNKSIZE 进行了修改后正好改变了分配块在内存中的排布顺序,从而消除了段错误
  • 同时最后的测试点有了较大幅度的提升,已经到 97%
    18
    随后尝试对 binary_bal 和 binary2_bal 测试点进行优化
    考虑按照块的大小选择 place 分配方式,即大块分在右边,而小块分在左边,分界线介于 80~110 字节之间
  • 但在修改完代码后发现
    19
    所有测试点均在第六行发生负载覆盖问题,通过查看测试点可以知道第一次 malloc 可以正常执行,但在进行第二次 malloc 后就覆盖掉了第一次 malloc 的内容
  • 调试发现
    20
    发现第一次将分配块(0xf697c118)的 ALLOC 置为 1,但在 第二次 malloc 时 ALLOC 被隐式地修改回了 0
    经过长时间的调试发现问题在于没有修改传入的 bp 指针,导致 bp 指针指向了错误的位置,而在之后 malloc 中影响到了之前的 ALLOC
    所以将 place 函数从 void 类型修改为 void * 类型,将修改后的 bp 指针传出,并在 malloc 函数中更新 bp 指针即可消除负载覆盖的问题
  • 但这时又再次出现了段错误的问题
    21
    经过排查发现是 coalesce2 函数中判断块的前部是否存在空闲块和 place 函数发生了冲突
    简单起见,直接暴力删除 coalesce2 中对于 flag==2 的情况,同时将 CHUNKSIZE 修改回 4096
  • 修改后最后两个测试点发生 error
    22
  • 考虑 coealesce2 函数和其他许多函数可能发生了冲突,最后选择直接删除 coalesce2 整个函数
    23
    分数提升到了 92 分
    最后通过重整代码和修改 CHUNSIZE 大小和初始堆的大小,成功再次提升 5 分
  • 最终实验结果
    24

# 完整代码

/*
 * mm-naive.c - The fastest, least memory-efficient malloc package.
 * 
 * In this naive approach, a block is allocated by simply incrementing
 * the brk pointer.  A block is pure payload. There are no headers or
 * footers.  Blocks are never coalesced or reused. Realloc is
 * implemented directly using mm_malloc and mm_free.
 *
 * NOTE TO STUDENTS: Replace this header comment with your own header
 * comment that gives a high level description of your solution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include "mm.h"
#include "memlib.h"
/*********************************************************
 * NOTE TO STUDENTS: Before you do anything else, please
 * provide your team information in the following struct.
 ********************************************************/
team_t team = {
    /* Team name */
    "ECNU SE",
    /* First member's full name */
    "Jack Qiu",
    /* First member's email address */
    "2055272094@qq.com",
    /* Second member's full name (leave blank if none) */
    "",
    /* Second member's email address (leave blank if none) */
    ""};
/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8
/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
#define WSIZE 4
#define DSIZE 8
#define CHUNKSIZE 4096
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define PACK(size, alloc) ((size) | (alloc))
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
#define HDRP(bp) ((char *)(bp)-WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char *)(bp)-WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp)-GET_SIZE(((char *)(bp)-DSIZE)))
#define PRED(bp) ((char *)(bp))         // 返回前驱结点
#define SUCC(bp) ((char *)(bp) + WSIZE) // 返回后继结点
static void *extend_heap(size_t words);
static void *coalesce(void *bp);
static void relink(void *bp);
static void *find_fit(size_t size);
static void* place(void *bp, size_t asize, int flag);
static int judge(int size);
static void updatenode(void *bp);
const int MAX_SIZE = 15;                // 空闲链表数组大小
static void *heap_listp = NULL;
static unsigned int *root[15];          // 空闲链表数组
static int judge(int size)
{
    // 相比于采用 log,考虑采用 if-else 语句提升速度
    int index = 0;
    if (size <= 8)          index = 3;  // 由于 8 字节对齐,size 不可能小于 8
    else if (size <= 16)    index = 4;
    else if (size <= 32)    index = 5;
    else if (size <= 64)    index = 6;
    else if (size <= 128)   index = 7;
    else if (size <= 256)   index = 8;
    else if (size <= 512)   index = 9;
    else if (size <= 1024)  index = 10;
    else if (size <= 2048)  index = 11;
    else if (size <= 4096)  index = 12;
    else                    index  =13;
    return index;
}
/* 
 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
    if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1)
        return -1;
    PUT(heap_listp, 0);
    PUT(heap_listp + (1 * WSIZE), PACK(DSIZE, 1));
    PUT(heap_listp + (2 * WSIZE), PACK(DSIZE, 1));
    PUT(heap_listp + (3 * WSIZE), PACK(0, 1));
    for (int i = 0; i < MAX_SIZE; i++)
        root[i] = NULL;             // 初始化空闲链表数组
    heap_listp += (4 * WSIZE);
    if (extend_heap(115) == NULL)   // 对于测试点进行特别优化
        return -1;
    return 0;
}
static void *extend_heap(size_t words)
{
    char *bp;
    size_t size;
    size = (words % 2) ? (words + 1) * DSIZE : words * DSIZE;   //WSIZE 修改为 DSIZE
    if ((long)(bp = mem_sbrk(size)) == -1)
        return NULL;
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    PUT(PRED(bp), 0);       // 前驱结点初始化
    PUT(SUCC(bp), 0);       // 后继节点初始化
    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));
    return coalesce(bp);
}
/* 
 * mm_malloc - Allocate a block by incrementing the brk pointer.
 *     Always allocate a block whose size is a multiple of the alignment.
 */
void *mm_malloc(size_t size)
{
    size_t asize;
    size_t extendsize;
    char *bp;
    if (size == 0)
        return NULL;
    if (size <= DSIZE)
        asize = 2 * (DSIZE);
    else
        asize = (DSIZE) * ((size + (DSIZE) + (DSIZE - 1)) / (DSIZE));
    if ((bp = find_fit(asize)) != NULL)
    {
        bp = place(bp, asize, 0);   // 更新 bp
        return bp;
    }
    extendsize = MAX(asize, CHUNKSIZE);
    if ((bp = extend_heap(extendsize / DSIZE)) == NULL)
        return NULL;
    bp = place(bp, asize, 0);       // 更新 bp
    return bp;
}
static void *find_fit(size_t size)
{
    for (int i = judge(size); i < MAX_SIZE; i++)    // 若查找不到依次向下一个下标移动
    {
        void *bp = root[i];     // 空闲链表内部查找
        while (bp != NULL)
        {
            if (size <= GET_SIZE(HDRP(bp)))
                return bp;
            bp = GET(SUCC(bp));
        }
    }
    return NULL;
}
static void *place(void *bp, size_t asize, int flag)
{
    size_t csize = GET_SIZE(HDRP(bp));
    if (!flag)          //malloc 调用时需要 relink,而 realloc 调用时不需要 relink
        relink(bp);
    if ((csize - asize) >= (2 * DSIZE)) // 空闲区域变为空闲块
    {
        // 分割区间可在 80 字节~110 字节之间浮动,对结果几乎无影响
        if (asize < 96) // 小于 96 字节
        {
            PUT(HDRP(bp), PACK(asize, 1));          // 分配块置 1
            PUT(FTRP(bp), PACK(asize, 1));
            bp = NEXT_BLKP(bp);
            PUT(HDRP(bp), PACK(csize - asize, 0));  // 空闲块置 0
            PUT(FTRP(bp), PACK(csize - asize, 0));
            PUT(PRED(bp), 0);   // 前驱初始化
            PUT(SUCC(bp), 0);   // 后继初始化
            updatenode(bp);     // 将空闲块插入空闲链表中
            return PREV_BLKP(bp);   // 返回分配块指针
        }
        else            // 大于等于 96 字节
        {
            PUT(HDRP(bp), PACK(csize - asize, 0));  // 空闲块置 0
            PUT(FTRP(bp), PACK(csize - asize, 0));
            PUT(HDRP(NEXT_BLKP(bp)), PACK(asize, 1));   // 分配块置 1
            PUT(FTRP(NEXT_BLKP(bp)), PACK(asize, 1));
            PUT(PRED(bp), 0);   // 前驱初始化
            PUT(SUCC(bp), 0);   // 后继初始化
            updatenode(bp);     // 将空闲块插入空闲链表中
            return NEXT_BLKP(bp);   // 返回分配块指针
        }
    }
    else    // 丢弃空闲区域
    {
        PUT(HDRP(bp), PACK(csize, 1));
        PUT(FTRP(bp), PACK(csize, 1));
        return bp;
    }
}
/*
 * mm_free - Freeing a block does nothing.
 */
void mm_free(void *ptr)
{
    size_t size = GET_SIZE(HDRP(ptr));
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));
    PUT(SUCC(ptr), 0);  // 前驱初始化
    PUT(PRED(ptr), 0);  // 后继初始化
    coalesce(ptr);
}
static void relink(void *bp)
{
    int index = judge(GET_SIZE(HDRP(bp)));
    void *before = GET(PRED(bp));
    void *after = GET(SUCC(bp));
    if (before == NULL)             // 头部插入
    {
        if (after != NULL)
            PUT(PRED(after), 0);    // 后块前驱置 0
        root[index] = after;        // 空闲数组更新头部
    }
    else
    {
        if (after != NULL)
            PUT(PRED(after), before);   // 后块前驱更新
        PUT(SUCC(before), after);   // 前块后驱更新
    }
    PUT(PRED(bp), 0);   // 前驱初始化
    PUT(SUCC(bp), 0);   // 后继初始化
}
static void *coalesce(void *bp)
{
    // 所有情况一律选择先删除后插入节点
    size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size = GET_SIZE(HDRP(bp));
    if (prev_alloc && !next_alloc)  //Case 2
    {
        size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
        relink(NEXT_BLKP(bp));  // 删除前面的空闲块
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    }
    else if (!prev_alloc && next_alloc) //Case 3 
    {
        size += GET_SIZE(HDRP(PREV_BLKP(bp)));
        relink(PREV_BLKP(bp));  // 删除后面的空闲块
        PUT(FTRP(bp), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        bp = PREV_BLKP(bp);
    }
    else if (!prev_alloc && !next_alloc)    //Case 4 
    {
        size += GET_SIZE(FTRP(NEXT_BLKP(bp))) + GET_SIZE(HDRP(PREV_BLKP(bp)));
        relink(PREV_BLKP(bp));  // 删除前面的空闲块
        relink(NEXT_BLKP(bp));  // 删除后面的空闲块
        PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        bp = PREV_BLKP(bp);
    }
    updatenode(bp); // 插入新的空闲块
    return bp;
}
static void updatenode(void *bp)
{
    void *now_root = root[judge(GET_SIZE(HDRP(bp)))];
    char *prevp = NULL;
    char *nextp = now_root;
    // 升序排列
    while (nextp != NULL)
    {
        if (GET_SIZE(HDRP(nextp)) >= GET_SIZE(HDRP(bp)))
            break;  // 确定插入位置
        prevp = nextp;
        nextp = GET(SUCC(nextp));
    }
    if (prevp == NULL)  // 头部插入
    {
        root[judge(GET_SIZE(HDRP(bp)))] = bp;
        PUT(SUCC(bp), nextp);
        PUT(PRED(bp), 0);
        if (nextp != NULL)
            PUT(PRED(nextp), bp);
    }
    else                // 中间插入
    {
        PUT(SUCC(prevp), bp);
        PUT(PRED(bp), prevp);
        PUT(SUCC(bp), nextp);
        if (nextp != NULL)
            PUT(PRED(nextp), bp);
    }
}
/*
 * mm_realloc - Implemented simply in terms of mm_malloc and mm_free
 */
void *mm_realloc(void *ptr, size_t size)
{
    if (ptr == NULL)    //ptr == NULL 直接 mm_malloc
        return mm_malloc(size);
    if (size == 0)      //size == 0 直接 free
    {
        mm_free(ptr);
        return NULL;
    }
    size_t asize;
    size_t copySize = GET_SIZE(HDRP(ptr));
    void *bp;
    if (size <= DSIZE)  //8 字节对齐
        asize = 2 * (DSIZE);
    else
        asize = (DSIZE) * ((size + (DSIZE) + (DSIZE - 1)) / (DSIZE));
    if (copySize >= asize)  // 小于原 ptr 大小抛弃空闲区域直接返回
        return ptr; 
    if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) && !GET_SIZE(HDRP(NEXT_BLKP(NEXT_BLKP(ptr)))))
    {
        // 判断后块是否为空闲块且空闲块是否为最后一块
        size_t new_size = GET_SIZE(HDRP(ptr)) + GET_SIZE(HDRP(NEXT_BLKP(ptr)));
        if (new_size < asize)   // 大小不够需要进行扩展堆
        {
            if (extend_heap(MAX(asize - new_size, CHUNKSIZE) == NULL))
                return NULL;    // 扩展失败
            new_size += MAX(asize - new_size, CHUNKSIZE);   // 更新大小
        }
        relink(NEXT_BLKP(ptr)); // 删除空闲块
        PUT(HDRP(ptr), PACK(new_size, 1));  // 更新原块大小
        PUT(FTRP(ptr), PACK(new_size, 1));
        return ptr;
    }
    // 上述条件均不满足时选择原始方法
    bp = mm_malloc(size);
    memcpy(bp, ptr, size);
    mm_free(ptr);
    return bp;
}