实验要求
实现一个堆上的动态内存分配器,达到最高性能
前期准备
- 框架代码是一个最快的、最低效率的malloc,直接运行默认测试集得到66分(以下评分均基于默认测试集)
- 书上给的代码是基于隐式空闲链表,使用立即边界标记合并、首次适配方式的简单分配器,我在CSAPP官网下载了书上的代码,运行得到的分数是75分
- 这里有个小插曲,无论是书上的代码还是网上已经实现的代码,直接运行都会显示“段错误”,经过某评论的启发,发现这里的代码应该是32位的,而我的环境是64位的,所以无法直接运行。解决方法如下
- 执行
sudo apt-get install gcc-multilib
完善编译环境 - 在
Makefile
中加上-m32
- 执行
- 根据书上内容,“因为块分配与堆块的总数呈线性关系,所以对于通用的分配器,隐式空闲链表是不合适的”,因此为了提高分配器的评分,我采用了分离适配的空闲链表算法,这种方法既快速,对内存的使用也很有效率
- 这一方法也是C标准库中提供的GNU malloc包采用的算法
算法思路
- 空闲块的header后存储两个指针,分别指向前一个空闲块和后一个空闲块(类似于双向链表)
- 分离适配的空闲链表,以2的幂次为界划分成链表数组(数组具体长度需要调参确定最优值),每一个链表内空闲块的大小均按递增的顺序排列,因此可以采用首次适配的方式来选择空闲块
- 当应用程序发起内存请求时,分配器首先检查是否有足够大的空闲块可用,如果有,则从相应的空闲链表中取出一个块进行分配;如果没有,它会扩展堆以创建一个足够大的块来满足请求
- 当内存被释放时,释放的块会与相邻的空闲块合并,然后被重新链接到相应的空闲链表中,以保持内存的连续性和提高内存利用效率
优化思路
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%
参考链接
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;
}