CSAPP:动态内存分配器

实验要求

实现一个堆上的动态内存分配器,达到最高性能

前期准备

  • 框架代码是一个最快的、最低效率的malloc,直接运行默认测试集得到66分(以下评分均基于默认测试集)
  • 书上给的代码是基于隐式空闲链表,使用立即边界标记合并首次适配方式的简单分配器,我在CSAPP官网下载了书上的代码,运行得到的分数是75分
    • alt text
  • 这里有个小插曲,无论是书上的代码还是网上已经实现的代码,直接运行都会显示“段错误”,经过某评论的启发,发现这里的代码应该是32位的,而我的环境是64位的,所以无法直接运行。解决方法如下
    • 执行sudo apt-get install gcc-multilib完善编译环境
    • Makefile中加上-m32
  • 根据书上内容,“因为块分配与堆块的总数呈线性关系,所以对于通用的分配器,隐式空闲链表是不合适的”,因此为了提高分配器的评分,我采用了分离适配的空闲链表算法,这种方法既快速,对内存的使用也很有效率
    • 这一方法也是C标准库中提供的GNU malloc包采用的算法

算法思路

  • 空闲块的header后存储两个指针,分别指向前一个空闲块和后一个空闲块(类似于双向链表)
    • alt text
  • 分离适配的空闲链表,以2的幂次为界划分成链表数组(数组具体长度需要调参确定最优值),每一个链表内空闲块的大小均按递增的顺序排列,因此可以采用首次适配的方式来选择空闲块
    • alt text
  • 当应用程序发起内存请求时,分配器首先检查是否有足够大的空闲块可用,如果有,则从相应的空闲链表中取出一个块进行分配;如果没有,它会扩展堆以创建一个足够大的块来满足请求
  • 当内存被释放时,释放的块会与相邻的空闲块合并,然后被重新链接到相应的空闲链表中,以保持内存的连续性和提高内存利用效率

优化思路

  • get_freelisthead,输出符合size大小的分离的空闲链表的起始位置
    • 常规的思路是用多个if判断语句来确定给定的size大小在哪个链表中
    • 稍微进阶一点的写法是利用下面这种三目运算符实现
     int i = (size <= 16)    ? 0 :
           (size <= 32)    ? 1 :
           (size <= 64)    ? 2 :
           (size <= 128)   ? 3 :
           (size <= 256)   ? 4 :
           (size <= 512)   ? 5 :
           (size <= 1024)  ? 6 :
           (size <= 2048)  ? 7 :
           (size <= 4096)  ? 8 : 9;
    
    • 更好的写法是利用移位运算来实现
	int shift = 4;
	while (shift < (SEG_LIST_COUNT + 3) && size > (1 << shift))
		shift++;
  • mm_realloc,重新分配
    • 常规的思路就是调用mm_malloc分配一个新空间,然后使用memcpy将原块中的数据复制过去,在把原来的块mm_free,但是其中复制数据的过程其实很浪费时间,如何避免复制数据?
      • 尽量保证数据不动,若空间不够可以把下一个空闲块拼接起来
    • 考虑下面三种情况
      • Case1:若原块大小更大,则返回原指针(若剩余空间超过最小块大小,将剩余空间回收)
      • Case2:若原块大小更小,考虑下一个块是否为空闲块,若加上下一个空闲块能满足要求,则把两个块拼接起来,返回原指针(若剩余空间超过最小块大小,将剩余空间回收)
      • Case3:若原块大小更小,且不能通过拼接下一个块达到要求,只能重新分配一个空间,复制原数据,再释放原空间

实验结果

  • 最终的评分是91分,比框架代码提高了37.9%
    • alt text

参考链接

CSAPP:Lab5-Malloc Lab - 知乎
六 Malloc Lab - 简书
GitHub - hehozo/Malloc-lab: CSAPP Malloc-lab: 91% performance index, my own dynamic memory allocator in C.
与 Malloc Lab(in csapp) 大战三天三夜纪实 - 知乎
CSapp lab5 MAlloc Lab以及段错误处理 && CSapp第九章学习 2 Linux虚拟内存系统与Linux进程虚拟地址空间 - TheDa - 博客园
CSAPP-malloclab 解题思路记录 - 找一个吃麦旋风的理由

完整代码

// 本实验采用分离适配的空闲链表算法,这是一种高效的内存管理技术,它通过维护多个不同大小的空闲链表来优化内存的分配和回收过程。
// 该算法将内存划分成一系列固定大小的块,这些块可以进一步细分以适应不同大小的内存请求。每个空闲链表根据块的大小进行分类,
// 每个分类中包含相同大小区间的空闲块。当应用程序发起内存请求时,分配器首先检查是否有足够大的空闲块可用,
// 如果有,则从相应的空闲链表中取出一个块进行分配;如果没有,它会扩展堆以创建一个足够大的块来满足请求。
// 当内存被释放时,释放的块会与相邻的空闲块合并,然后被重新链接到相应的空闲链表中,以保持内存的连续性和提高内存利用效率。
// 这种算法的优点在于其快速响应内存请求的能力,并通过合并空闲块减少了内存碎片,从而显著提升了内存的使用效率。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <string.h>

#include "mm.h"
#include "memlib.h"

/*********************************************************
 * 亲们请注意:开始之前,请把下面的信息修改为你的个人信息
 ********************************************************/
team_t team = {
    /* 团队名字 */
    "Lane",
    /* 团队老大的名字 */
    "Lane",
    /* 团队老大的email地址 */
    "laneljc@qq.com",
    /* 团队其他成员的名字 (如果没有,就空着) */
    "",
    /* 团队其他成员的email地址 (如果没有,就空着) */
    ""};

/* 常数和宏定义 */
#define WSIZE 4             // 字(bytes)
#define DSIZE 8             // 双字(bytes)
#define CHUNKSIZE (1 << 12) // 按此数量(字节)扩展堆
#define SEG_LIST_COUNT 22   // 分离的空闲链表长度(对不同测试集其最优值不同,需要调参)

#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 GET_PREV(p) (*(unsigned int *)(p))
#define SET_PREV(p, prev) (*(unsigned int *)(p) = (prev))
#define GET_NEXT(p) (*((unsigned int *)(p) + 1))
#define SET_NEXT(p, val) (*((unsigned int *)(p) + 1) = (val))

// 获取分离的空闲链表的index位
#define SEG_LIST(index) *((char **)segregate_starter + (index))

// 全局变量
static char *heap_listp = 0;        // 堆的起始位置
static char *segregate_starter = 0; // 分离的空闲链表起始位置

// 函数声明
static void *extend_heap(size_t words);
static void *place(void *bp, size_t asize);
static void *find_fit(size_t asize);
static void *coalesce(void *bp);
static size_t get_asize(size_t size);
void *get_freelisthead(size_t size);
static void remove_from_free_list(void *bp);
static void insert_to_free_list(void *bp);

// get_asize - 输入size,输出对齐且包含头尾字段后的asize
static size_t get_asize(size_t size)
{
    size_t asize;
    if (size <= DSIZE)
        asize = 2 * DSIZE;
    else
        asize = DSIZE * ((size + (DSIZE) + (DSIZE - 1)) / DSIZE);
    return asize;
}

// get_freelisthead - 输出符合size大小的分离的空闲链表的起始位置
// 优化重点!
void *get_freelisthead(size_t size)
{
    int shift = 4;
    while (shift < (SEG_LIST_COUNT + 3) && size > (1 << shift))
        shift++;
    return &(SEG_LIST(shift - 4));
}

// remove_from_free_list - 从分离的空闲链表中删除空闲块
static void remove_from_free_list(void *bp)
{
    if (!bp || GET_ALLOC(HDRP(bp)))
        return;

    void *root = get_freelisthead(GET_SIZE(HDRP(bp)));
    void *prev = GET_PREV(bp);
    void *next = GET_NEXT(bp);

    // 即删除root结点
    if (!prev)
    {
        if (next)
        {
            SET_NEXT(bp, NULL);
            SET_PREV(next, NULL);
        }
        PUT(root, next);
    }
    // 删除中间或尾节点
    else
    {
        SET_PREV(bp, NULL);
        if (next)
        {
            SET_NEXT(bp, NULL);
            SET_PREV(next, prev);
        }
        SET_NEXT(prev, next);
    }
}

// insert_to_free_list - 在分离的空闲链表中插入空闲块
static void insert_to_free_list(void *bp)
{
    if (!bp || GET_ALLOC(HDRP(bp)))
        return;

    size_t bp_size = GET_SIZE(HDRP(bp));
    void *root = get_freelisthead(bp_size);
    void *prev = root;
    void *next = GET(root);

    while (next)
    {
        if (GET_SIZE(HDRP(next)) >= bp_size)
            break;
        prev = next;
        next = GET_NEXT(next);
    }
    // 插入到头结点
    if (prev == root)
    {
        PUT(root, bp);
        SET_PREV(bp, NULL);
        SET_NEXT(bp, next);
        if (next != NULL)
            SET_PREV(next, bp);
    }
    // 插入到中间或尾结点
    else
    {
        SET_PREV(bp, prev);
        SET_NEXT(bp, next);
        SET_NEXT(prev, bp);
        if (next != NULL)
            SET_PREV(next, bp);
    }
}

// extend_heap - 用空闲块扩展堆并返回其块指针
static void *extend_heap(size_t words)
{
    char *bp;
    size_t size;

    // 对齐
    size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE;
    if ((long)(bp = mem_sbrk(size)) == -1)
        return NULL;

    // 初始化空闲块
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    SET_PREV(bp, 0);
    SET_NEXT(bp, 0);

    // 设置结尾块
    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));

    // 合并空闲块(并将空闲块插入链表)
    return coalesce(bp);
}

// place - 将区块bp从链表中移除,仅当剩余部分的大小等于或大于最小块的大小时,才进行分割
static void *place(void *bp, size_t asize)
{
    size_t csize = GET_SIZE(HDRP(bp));
    remove_from_free_list(bp);
    void *new_bp;

    // 分割
    if ((csize - asize) >= (2 * DSIZE))
    {
        if ((csize - asize) >= 200) // 莫名其妙的优化点:取前面的块还是后面的块有讲究
        {
            PUT(HDRP(bp), PACK(csize - asize, 0));
            PUT(FTRP(bp), PACK(csize - asize, 0));
            new_bp = NEXT_BLKP(bp);
            PUT(HDRP(new_bp), PACK(asize, 1));
            PUT(FTRP(new_bp), PACK(asize, 1));
            coalesce(bp);
            return new_bp;
        }
        else
        {
            PUT(HDRP(bp), PACK(asize, 1));
            PUT(FTRP(bp), PACK(asize, 1));
            new_bp = NEXT_BLKP(bp);
            PUT(HDRP(new_bp), PACK(csize - asize, 0));
            PUT(FTRP(new_bp), PACK(csize - asize, 0));
            coalesce(new_bp);
            return bp;
        }
    }
    // 不分割
    else
    {
        PUT(HDRP(bp), PACK(csize, 1));
        PUT(FTRP(bp), PACK(csize, 1));
        return bp;
    }
}

// find_fit - 找到一个适合大小为asize字节的块
static void *find_fit(size_t asize)
{
    // First-fit
    for (void *root = get_freelisthead(asize); root != &(SEG_LIST(SEG_LIST_COUNT - 1)); root += WSIZE)
    {
        void *bp = GET(root);
        while (bp)
        {
            if (GET_SIZE(HDRP(bp)) >= asize)
                return bp;
            bp = GET_NEXT(bp);
        }
    }
    // 没有适合的空闲块
    return NULL;
}

// coalesce - 合并相邻空闲块
static void *coalesce(void *bp)
{
    void *prev_blkp = PREV_BLKP(bp);
    void *next_blkp = NEXT_BLKP(bp);
    size_t prev_alloc = GET_ALLOC(FTRP(prev_blkp));
    size_t next_alloc = GET_ALLOC(HDRP(next_blkp));
    size_t size = GET_SIZE(HDRP(bp));

    // Case 1
    if (prev_alloc && next_alloc)
    {
        ;
    }
    // Case 2
    else if (prev_alloc && !next_alloc)
    {
        remove_from_free_list(next_blkp);
        size += GET_SIZE(HDRP(next_blkp));
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    }
    // Case 3
    else if (!prev_alloc && next_alloc)
    {
        remove_from_free_list(prev_blkp);
        size += GET_SIZE(HDRP(prev_blkp));
        PUT(FTRP(bp), PACK(size, 0));
        PUT(HDRP(prev_blkp), PACK(size, 0));
        bp = prev_blkp;
    }
    // Case 4
    else
    {
        remove_from_free_list(prev_blkp);
        remove_from_free_list(next_blkp);
        size += GET_SIZE(HDRP(prev_blkp)) + GET_SIZE(FTRP(next_blkp));
        PUT(HDRP(prev_blkp), PACK(size, 0));
        PUT(FTRP(next_blkp), PACK(size, 0));
        bp = prev_blkp;
    }
    // 这里已包含插入空闲块到链表,所以不必再调用插入函数
    insert_to_free_list(bp);
    return bp;
}

// mm_init - 初始化malloc模拟器
int mm_init(void)
{
    // 创建空闲链表的空间
    if ((segregate_starter = mem_sbrk(SEG_LIST_COUNT * WSIZE)) == (void *)-1)
        return -1;

    // 创建堆的空间
    if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1)
        return -1;

    // 初始化空闲链表
    for (int i = 0; i < SEG_LIST_COUNT; i++)
        SEG_LIST(i) = 0;

    // 初始化堆
    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);

    // 用CHUNKSIZE字节的空闲块扩展空堆
    if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
        return -1;
    return 0;
}

// mm_malloc - 在空闲块列表中查找空闲块,如果没有空闲块,则扩展堆
void *mm_malloc(size_t size)
{
    size_t asize = get_asize(size);
    size_t extendsize;
    char *bp;

    // 边界情况处理
    if (heap_listp == 0)
        mm_init();
    if (size == 0)
        return NULL;

    // 在空闲链表中查找
    if ((bp = find_fit(asize)) != NULL)
        return place(bp, asize);

    // 如果找不到,则扩展堆
    extendsize = MAX(asize, CHUNKSIZE);
    if ((bp = extend_heap(extendsize / WSIZE)) == NULL)
        return NULL;
    return place(bp, asize);
}

// mm_free - 释放块
void mm_free(void *bp)
{
    if (bp == 0)
        return;
    size_t size = GET_SIZE(HDRP(bp));
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    SET_PREV(bp, NULL);
    SET_NEXT(bp, NULL);
    coalesce(bp);
}

// mm_realloc - 重新分配
// Case1:若原块大小更大,则返回原指针(若剩余空间超过最小块大小,将剩余空间回收)
// Case2:若原块大小更小,考虑下一个块是否为空闲块,若加上下一个空闲块能满足要求,则把两个块拼接起来,返回原指针(若剩余空间超过最小块大小,将剩余空间回收)
// Case3:若原块大小更小,且不能通过拼接下一个块达到要求,只能重新分配一个空间,复制原数据,再释放原空间
// 优化重点:尽量少使用memcpy(即上述Case3)
void *mm_realloc(void *ptr, size_t size)
{
    void *newptr;
    void *nextptr;
    size_t oldsize, asize = get_asize(size), nsize;

    // 特殊情况处理
    // 即free
    if (size == 0)
    {
        mm_free(ptr);
        return NULL;
    }
    // 即malloc
    if (ptr == NULL)
        return mm_malloc(size);

    // 原块大小
    oldsize = GET_SIZE(HDRP(ptr));

    // Case 1
    if (asize <= oldsize)
    {
        if (oldsize - asize <= 2 * DSIZE)
            return ptr;
        PUT(HDRP(ptr), PACK(asize, 1));
        PUT(FTRP(ptr), PACK(asize, 1));
        newptr = ptr;
        nextptr = NEXT_BLKP(newptr);
        PUT(HDRP(nextptr), PACK(oldsize - asize, 0));
        PUT(FTRP(nextptr), PACK(oldsize - asize, 0));
        coalesce(nextptr);
        return newptr;
    }

    // Case 2
    nextptr = NEXT_BLKP(ptr);
    if (nextptr && !GET_ALLOC(HDRP(nextptr)))
    {
        nsize = GET_SIZE(HDRP(nextptr));
        if (nsize + oldsize >= asize)
        {
            remove_from_free_list(nextptr);
            if (oldsize + nsize - asize <= 2 * DSIZE)
            {
                PUT(HDRP(ptr), PACK(oldsize + nsize, 1));
                PUT(FTRP(ptr), PACK(oldsize + nsize, 1));
                return ptr;
            }
            else
            {
                PUT(HDRP(ptr), PACK(asize, 1));
                PUT(FTRP(ptr), PACK(asize, 1));
                newptr = ptr;
                ptr = NEXT_BLKP(newptr);
                PUT(HDRP(ptr), PACK(oldsize + nsize - asize, 0));
                PUT(FTRP(ptr), PACK(oldsize + nsize - asize, 0));
                coalesce(ptr);
                return newptr;
            }
        }
    }

    // Case 3
    newptr = mm_malloc(asize);
    if (newptr == NULL)
        return NULL;
    memcpy(newptr, ptr, oldsize);
    mm_free(ptr);
    return newptr;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值