csapp-malloclab

主要尝试了两种方法,隐式空闲链表(implicit free list)和隔离空闲链表(segregated free list),当然还有一种显式空闲链表法,但是感觉类似于单个的隔离空闲链表。

隐式空闲链表

每一段内存空间都有一个header和一个footer记录这段内存空间的大小和存储状态。不同的分配策略对得分影响非常大,first fit只有53分,改成next fit后有84分。

宏定义

#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 MAXHEAP (20 * (1 << 20))
#define BLOCK (1 << 12)
#define WSIZE 4
#define DSIZE 8
#define GET(x) (*((unsigned int*)(x)))
#define PUT(x, y) (*((unsigned int*)(x)) = (y))
#define PACK(x, y) ((x) | (y))
#define GETSZ(x) (GET(x) & (~0x7))
#define GETAL(x) (GET(x) & 0x1)
#define HD(x) ((x) - WSIZE)
#define FT(x) ((x) + GETSZ(HD(x)) - DSIZE)
#define PRE(x) ((x) - GETSZ((x) - DSIZE))
#define SUF(x) ((x) + GETSZ(HD(x)))
#define max(a, b) ((a) > (b) ? (a) : (b))

一部分宏定义和书上略有不同,但功能基本一样。

堆扩张

堆扩张要求双字对齐,所以如果字数为单数则加一。

void *extend(size_t words) {
    void *bp;
    if (words & 1) words++;
    size_t size = words * WSIZE;
    if ((long)(bp = mem_sbrk(size)) == -1)
        return (void *)-1;
    PUT(HD(bp), PACK(size, 0));
    PUT(FT(bp), PACK(size, 0));
    PUT(HD(SUF(bp)), PACK(0, 1));
    bp = combine(bp);
    return bp;
}

初始化

int mm_init(void)
{
    head = mem_sbrk(4 * WSIZE);
    PUT(head, PACK(0, 1));
    PUT(head + 1 * WSIZE, PACK(DSIZE, 1));
    PUT(head + 2 * WSIZE, PACK(DSIZE, 1));
    PUT(head + 3 * WSIZE, PACK(0, 1));
    head += 2 * WSIZE;
    if (extend(BLOCK / WSIZE) == (void *)-1)
       return -1;
    return 0;
}

在最前面设置一个大小为8个字节,只有一个header和一个footer的已分配空间作为边界,并将链表头设置在这块空间上。

分配

分配时需要判断空闲块大小减去需要的内存大小后,剩下的部分是否能作为一个新的空闲块。

因为一个空闲块需要包含header和footer,所以这要求每一个空闲块至少为8个字节。

这里先用的是first fit的方式,也就是从链表头开始,找到第一个空间足够的地址,效率较低。

void *find_fit(size_t size) {
    void *bp;
    for (bp = head; GETSZ(HD(bp)) > 0; bp = SUF(bp)) {
        if (!GETAL(HD(bp)) && GETSZ(HD(bp)) >= size) {
            return bp;
        }
    }
    size_t newsize = max(BLOCK, size);
    bp = extend(newsize / WSIZE);
    return bp;
}

void *mm_malloc(size_t size) {
    int newsize = ALIGN(size + SIZE_T_SIZE);
    void *bp = find_fit(newsize);
    if (bp == (void *)-1) return bp;
    size_t space = GETSZ(HD(bp));
    PUT(HD(bp), PACK(space, 1));
    PUT(FT(bp), PACK(space, 1));
    if (space - newsize >= DSIZE) {
        PUT(HD(bp), PACK(newsize, 1));
        PUT(FT(bp), PACK(newsize, 1));
        PUT(HD(SUF(bp)), PACK(space - newsize, 0));
        PUT(FT(SUF(bp)), PACK(space - newsize, 0));
    }
    return bp;
}

也可以改成next fit的分配策略,效率较高(同时释放部分也要作相应修改,避免prefit指向不存在的内存地址)

void *next_fit(size_t size) {
    void *bp;
    for (bp = prefit; GETSZ(HD(bp)) > 0; bp = SUF(bp)) {
        if (!GETAL(HD(bp)) && GETSZ(HD(bp)) >= size) {
            prefit = bp;
            return bp;
        }
    }
    for (bp = head; bp != prefit; bp = SUF(bp)) {
        if (!GETAL(HD(bp)) && GETSZ(HD(bp)) >= size) {
            prefit = bp;
            return bp;
        }
    }
    size_t newsize = max(size, CHUNKSIZE);
    bp = extend(newsize / WSIZE);
    prefit = bp;
    return bp;
}

合并

void combine_with_suffix(void *ptr) {
    size_t suf_size = GETSZ(HD(SUF(ptr)));
    if (prefit == SUF(ptr)) prefit = ptr;
    PUT(FT(ptr), 0);
    PUT(HD(SUF(ptr)), 0);
    PUT(HD(ptr), PACK(GETSZ(HD(ptr)) + suf_size, 0));
    PUT(FT(ptr), PACK(GETSZ(HD(ptr)), 0));
}

void *combine(void *ptr) {
    size_t pre_alloc = GETAL(HD(PRE(ptr)));
    size_t suf_alloc = GETAL(HD(SUF(ptr)));
    if (pre_alloc && suf_alloc)
        return ptr;
    if (pre_alloc && !suf_alloc) {
        combine_with_suffix(ptr);
        return ptr;
    }
    if (!pre_alloc && suf_alloc) {
        void *bp = PRE(ptr);
        combine_with_suffix(PRE(ptr));
        return bp;
    }
    if (!pre_alloc && !suf_alloc) {
        void *bp = PRE(ptr);
        combine_with_suffix(ptr);
        combine_with_suffix(PRE(ptr));
        return bp;
    }
    return (void *)-1;
}

如果当前空闲块前后的内存空间也是空闲的,那么就合并成一个空闲块。

释放

void mm_free(void *ptr)
{
    size_t size = GETSZ(HD(ptr));
    PUT(HD(ptr), PACK(size, 0));
    PUT(FT(ptr), PACK(size, 0));   
    combine(ptr);
}

再分配

没有特殊处理,就是free+malloc+memcpy

void *mm_realloc(void *ptr, size_t size)
{
    void *oldptr = ptr;
    void *newptr;
    size_t copySize;
    newptr = mm_malloc(size);
    if (newptr == NULL)
      return NULL;
    copySize = GETSZ(HD(oldptr));
    if (size < copySize)
      copySize = size;
    memcpy(newptr, oldptr, copySize);
    mm_free(oldptr);
    return newptr;
}

隔离空闲链表

这个方法相对来说比较复杂,但得分也可以进一步提高到90。

思路就是创建多个链表,把所有空闲段按照大小分到不同的链表里,跟显式分配一样,空闲块的前两个字分别存放的是链表前驱和后继的内存地址。这样的好处是分配内存时,寻址的时间减少,但缺点是内存碎片可能较多,内存空间利用率较低。

一个优化是在分配内存时,判断需要分配的内存大小是否超过某个临界值(这里根据测试选择的是96),若超过了则放在空闲段的尾部,把前半段切割出来,否则放在头部。

原理是这样可以使得较大的块相对集中, 较小的块也相对集中,不容易出现两个大块之间被某个小块隔开的情况,可以减少内存碎片。

根据空间大小找对应链表是通过get_target函数实现的,其实就是取以2为底的对数。

其他的好像也没啥了。

/*
 *  Segregated free list.
 *  scores:90/100
 */
#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 */
    "Yunfan Li",
    /* First member's full name */
    "Yunfan Li",
    /* First member's email address */
    "10195501416@stu.ecnu.edu.cn",
    /* 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 MAXLIST 25

/* Basic constants and macros */
#define WSIZE 4 /* Word and header/footer size (bytes) */
#define DSIZE 8 /* Double word size (bytes) */
#define CHUNKSIZE (1<<12) /* Extend heap by this amount (bytes) */
#define INITCHUNKSIZE (1<<8)

#define MAX(x, y) ((x) > (y)? (x) : (y))
/* Pack a size and allocated bit into a word */
#define PACK(size, alloc) ((size) | (alloc))

/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))

/* Read the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)

/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)

/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE)))

/* for explict linkedlist */
#define PRED_PTR(ptr) ((char *)(ptr))
#define SUCC_PTR(ptr) ((char *)(ptr) + WSIZE)

#define PRED(ptr) (*(char **)(ptr))
#define SUCC(ptr) (*(char **)(SUCC_PTR(ptr)))


#define SET_PTR(p, ptr) (*(unsigned int *)(p) = (unsigned int)(ptr))

inline static size_t get_asize(size_t size);
inline static size_t get_target(size_t size);
static void *extend_heap(size_t words);
static void *place(void *bp, size_t asize);
static void *coalesce(void *bp);
static void insert_node(void *bp);
static void delete_node(void *bp);

void *seg_free_lists[MAXLIST];
void *heap_listp;

int mm_init(void) {
    for (size_t i = 0; i < MAXLIST; i++) {
        seg_free_lists[i] = NULL;
        }
    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));
    heap_listp += 2 * WSIZE;

    if (extend_heap(INITCHUNKSIZE/WSIZE) == NULL)
        return -1;
    return 0;
}

void *mm_malloc(size_t size)
{
    size_t asize, search;
    size_t extendsize;
    char *bp = NULL;
    if (size == 0)
        return NULL;
    asize = get_asize(size);
    size_t target = get_target(asize);
    for (; target < MAXLIST; target++) {
        if ((seg_free_lists[target] == NULL)) continue;
        char *i = seg_free_lists[target];
        for(; i != NULL; i = SUCC(i))
        {
            if (GET_SIZE(HDRP(i)) < asize) continue;
                bp = i;
                break;
        }
        if (bp != NULL) break;
    }
    if (bp == NULL) {
        extendsize = MAX(asize,CHUNKSIZE);
        if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
            return NULL;
    }
    bp = place(bp, asize);
    return bp;
}

void mm_free(void *bp)
{
    size_t size = GET_SIZE(HDRP(bp));
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    coalesce(bp);
}

void *mm_realloc(void *ptr, size_t size)
{
    void *oldptr = ptr;
    void *newptr;
    size_t copySize;
    newptr = mm_malloc(size);
    if (newptr == NULL)
        return NULL;
    copySize = GET_SIZE(HDRP(oldptr));
    if (size < copySize)
        copySize = size;
    memcpy(newptr, oldptr, copySize);
    mm_free(oldptr);
    return newptr;
}

inline static size_t get_target(size_t size) {
    size_t tar = 0;
    for (size_t j = size; (j > 1) && tar < MAXLIST; j >>= 1) tar++;
        return tar;
}

inline static size_t get_asize(size_t size) 
{
    size_t asize;
    if(size < DSIZE){
        asize = 2 * DSIZE;
    }else{
        asize = ALIGN(size + DSIZE);
    }
    return asize;
}

static void *extend_heap(size_t words)
{
    size_t size = (words % 2) ? words + 1 : words;
    size *= WSIZE;
    char *bp = mem_sbrk(size);

    if (bp == (void *)-1) return NULL;

    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));

    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));
    bp = coalesce(bp);
    return bp;
}

static void *coalesce(void *bp) {
    size_t prev_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size;
    if (!next_alloc && !prev_alloc) {
        size = GET_SIZE(HDRP(bp)) + GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(HDRP(NEXT_BLKP(bp)));
        delete_node(PREV_BLKP(bp));
        delete_node(NEXT_BLKP(bp));
        bp = PREV_BLKP(bp);
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    } else if (!next_alloc && prev_alloc) {
        size = GET_SIZE(HDRP(bp)) + GET_SIZE(HDRP(NEXT_BLKP(bp)));
        delete_node(NEXT_BLKP(bp));
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    } else if (next_alloc && !prev_alloc) {
        size = GET_SIZE(HDRP(bp)) + GET_SIZE(HDRP(PREV_BLKP(bp)));
        delete_node(PREV_BLKP(bp));
        bp = PREV_BLKP(bp);
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    }
    insert_node(bp);
    return bp;
}

static void *place(void *bp, size_t asize)
{
    size_t size = GET_SIZE(HDRP(bp));
    delete_node(bp);
    if ((size - asize) < 2 * DSIZE) {
        PUT(HDRP(bp),PACK(size,1));
        PUT(FTRP(bp),PACK(size,1));
    } else if (asize >= 96) {
        PUT(HDRP(bp),PACK(size - asize,0));
        PUT(FTRP(bp),PACK(size - asize,0));
        PUT(HDRP(NEXT_BLKP(bp)),PACK(asize,1));
        PUT(FTRP(NEXT_BLKP(bp)),PACK(asize,1));
        insert_node(bp);
        return NEXT_BLKP(bp);
    } else {
        PUT(HDRP(bp),PACK(asize,1));
        PUT(FTRP(bp),PACK(asize,1));
        PUT(HDRP(NEXT_BLKP(bp)),PACK(size - asize,0));
        PUT(FTRP(NEXT_BLKP(bp)),PACK(size - asize,0));
        insert_node(NEXT_BLKP(bp));
    }
    return bp;
}

static void insert_node(void *bp)
{
    size_t size = GET_SIZE(HDRP(bp));
    size_t tar = get_target(size);
    void *succ = seg_free_lists[tar];
    seg_free_lists[tar] = bp;
    if (succ != NULL) SET_PTR(PRED_PTR(succ), bp);
    SET_PTR(SUCC_PTR(bp), succ);
    SET_PTR(PRED_PTR(bp), NULL);
}

static void delete_node(void *bp) 
{ 
    if (PRED(bp) != NULL) 
        SET_PTR(SUCC_PTR(PRED(bp)), SUCC(bp));
    else {
        size_t size = GET_SIZE(HDRP(bp));
        size_t tar = get_target(size);
        seg_free_lists[tar] = SUCC(bp);
    }
    if (SUCC(bp) != NULL) SET_PTR(PRED_PTR(SUCC(bp)), PRED(bp));
}