Lab7 Malloc Lab

Lab7 Malloc Lab

写在前言:这个实验的来源是CSAPP官网:CSAPP Labs ,如果感兴趣的话,可以点击这个链接🔗去下载。实验中的10个traces文件是没有附加的,可以点击这个🔗:traces file 自行下载。

实验说明

Malloc Lab实验要求我们实现一个动态内存分配器(Dynamic Memory Allocator),要求我们实现与标准C库中的mallocfreerealloc 具有相同功能的函数,可以自行定义块的空间结构。

唯一需要修改的文件是mm.c,里面声明了以下四个函数:

int mm_init(void);
void *mm_malloc(size_t size);
void mm_free(void *ptr);
void *mm_realloc(void *ptr, size_t size);
  • mm_init:在调用其它三个函数之前,mdriver会先调用 mm_init 进行必要的初始化,比如分配初始堆区,如果初始化不成功,返回-1,否则返回0。
  • mm_malloc:返回已分配块的有效载荷payload的起始地址,其中,payload的大小至少为size字节,除此之外,必须保证已分配块在堆区里面,并且不会与其它已分配块重叠。
  • mm_free:释放被ptr指向的已分配块(调用mm_mallocmm_realloc得到的ptr),如果已经被释放,Nothing to do。
  • mm_realloc:返回一个已分配的块,其有效载荷payload的大小至少为size字节,并且有如下的限制:
    • 如果ptrNULL,那么等价于调用mm_alloc(size)
    • 如果size等于0,那么的等价于调用mm_free(ptr)
    • 否则,mm_realloc必须重新分配payload至少为size字节的块,并且保证新的块的内容和旧的块一致,值得注意的是,这个新块的地址有可能和旧块的地址相同,或者不同,取决于我们的实现方式。

性能评价指标

对于给定的n个分配和释放请求的某种顺序
R 0 , R 1 , ⋯   , R k ⋯   , R n − 1 R_0,R_1,\cdots,R_k\cdots,R_{n-1} R0,R1,,Rk,Rn1
如果一个应用程序请求一个p字节的块,那么得到的已分配块的有效载荷(payload)就是p字节,在请求 R k R_k Rk完成后,聚集有效载荷(aggregate payload)表示为 P k P_k Pk,为当前已分配块的有效载荷之和, H k H_k Hk表示为堆当前的大小(单调非递减)。

那么前k+1请求的 峰值利用率,表示为 U k = max ⁡ i ≤ k P i H k U_k=\frac{\max_{i\le k}P_i}{H_k} Uk=HkmaxikPi,越接近1表示空间利用率越高。

其次,吞吐率,表示单位时间内完成的请求个数,比如,如果分配器1秒内完成了500个分配请求和500个释放请求,那么它的吞吐率就是每秒1000次操作。

总的来说,分配器的目标就是在整个序列中使得峰值利用率 U n − 1 U_{n-1} Un1最大化,并且峰值利用率和吞吐率是互相牵制的,找到一个合适的折中就显得很重要。

mdriver会记录空间利用率和吞吐率,与标准C库的吞吐率进行比较,最终评价指标为:
P = w U + ( 1 − w ) min ⁡ ( 1 , T T l i b c ) P=wU+(1-w)\min(1,\frac{T}{T_{libc}}) P=wU+(1w)min(1,TlibcT)
其中, w = 0.6 w=0.6 w=0.6,这意味着空间利用率的占比更高。

相关知识

下图所示为用户内存空间的结构,动态内存分配器管理的是堆区。

在这里插入图片描述

碎片现象

为了提高分配器的性能,首先就要提高内存的空间利用率,需要理解造成空间利用率低的原因:碎片现象(Fragmentation),分为内部碎片和外部碎片。

  • 内部碎片,就是在一个已分配的块比有效载荷大的时候发生的,与块的空间结构,如是否有头部块和尾部块,或者与填充一些字节以满足对齐要求等有关。
  • 外部碎片,当空闲内存合计足够一个分配请求,但是没有一个单独的空闲块足够大满足这个请求发生的。

内存分配器的实现

  • 空闲块组织:如何记录空闲块?
  • 放置:如何选择一个合适的空闲块放置一个新分配的块(首次适配、下一次适配、最佳适配)
  • 分割:在一个新分配快放置到某一个空闲块之后,如何处理空闲块的剩余部分?
  • 合并:如何处理一个刚被释放的块。
空闲块组织

任何实际的分配器都需要一些数据结构,允许它来区别块的边界一级区别已分配块和空闲块。

一个简单的堆块形式如下:
在这里插入图片描述

上述块的结构包括一个字的头部、有效载荷以及用于对齐而进行的填充组成的。

在这里插入图片描述

这种块结构间接地形成了一种链表结构,叫隐式空闲链表,它的结构是比较简单的。

放置已分配的块

当一个应用请求一个k字节的块,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块,分配器执行这种搜索的方式由 放置策略 确定,常见的策略:首次适配,下一次适配,最佳适配

  • 首次适配,从头开始搜索空闲链表,选择第一个合适的空闲块
  • 下一次适配,从上一次查询结束的地方开始,选择第一个合适的空闲块
  • 最佳适配,检查每一个空闲块,选择所需请求大小的最小空闲块

实际上,下一次适配比首次适配运行明显要快一些,但是内存利用率比首次适配利用率低很多,在简单的空闲链表组织结构中,比如隐式空闲链表中,使用最佳适配的缺点是它要求对堆进行彻底的搜索。

带边界标记的合并

Knuth提出了一种技术叫边界标记,允许在常数时间内继续块合并操作。

在这里插入图片描述

合并的情况分为四种:

  1. 前面的块和后面都是已分配的,Nothing to do。
  2. 前面的块是已分配的,后面的块是空闲的。
  3. 前面的块是空闲的,而后面的块是已分配的。
  4. 前面的和后面的块都是空闲的。
    在这里插入图片描述

边界标记的概念很简单,对于不同类型的分配器和空闲链表的组织都是通用的,然而,它也存在一个潜在的缺陷,要求每个块都保持一个头部和脚部,如果应用请求很多小块时,会产生显著的内存开销。

对这个方法进行优化,可以使得已分配的块中不再需要脚部,只有在前面的块是空闲的时候才需要有脚部,如果把前面块的已分配/空闲位存放在当前块中多出来的低位中,那么已分配块就不需要脚部了,这样就可以讲个多出来的空间用作有效载荷了,需要注意的是,空闲块仍然需要脚部。

使用隐式空闲链表

第一种实现方式就是书本上给出的简单分配器实现,使用的块结构为未优化的带边界标记的堆块形式。

但是CSAPP书本上没有给出realloc的实现方式,这需要我们自行实现。

在这里插入图片描述

这里的隐式空闲链表的结构包括序言快、若干个普通块(已分配/空闲)、以及一个结尾块,值得注意的是,添加序言块和结尾块实现是比较方便的,比如,在合并操作时,不需要进行边界的处理,结尾块是一个大小为0的已分配块,它标记着堆的结束位置。

首先是,操作链表的基本常数和宏定义:

/* Base 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 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)))

/* Heap prologue block pointer*/
static void *heap_listp;

创建初始空闲链表(init)

mm_init函数创建一个初始的堆,分配序言块和尾块,并且将其扩展,创建一个大小为4096字节的初始堆区。

/* 
 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
   
    /* Create the initial empty heap */
    if ((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1)
    	return -1;
    
    PUT(heap_listp, 0);								/* Alignment padding */
    PUT(heap_listp + (1*WSIZE), PACK(DSIZE, 1));	/* Prologue header */
    PUT(heap_listp + (2*WSIZE), PACK(DSIZE, 1));	/* Prologue footer */
    PUT(heap_listp + (3*WSIZE), PACK(0, 1));		/* Epilogue header */
    heap_listp += (2*WSIZE);
    
    /* Extend the empty heap with a free block of CHUNKSIZE bytes */
    if (extend_heap(CHUNKSIZE/WSIZE) == NULL) {
   
    	return -1;
   	}

    return 0;
}

/* extend_heap -- Extend heap */
static void *extend_heap(size_t words)
{
   
	char *bp;
	size_t size;
	
	/* Allocate an even number of words to maintain alignment */
	size = (words % 2) ? (words+1) * WSIZE : words * WSIZE;
	if ((long)(bp = mem_sbrk(size)) == -1)
		return NULL;
	
	/* Initialize free block header/footer and the epilogue header */
	PUT(HDRP(bp), PACK(size, 0));			/* Free block header */
	PUT(FTRP(bp), PACK(size, 0));			/* Free block footer */
	PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));	/* New epilogue header */
	
	/* Coalesce if the previous block was free */
	return coalesce(bp);
}

释放和合并块(free)

mm_free释放一个块就是简单地把它的头部和尾部块设置为空闲即可,大小为这个块的大小,并且进行空闲块合并的操作。

/*
 * mm_free - Freeing a block and coalesce free block if can .
 */
void mm_free(void *ptr)
{
   
	size_t size = GET_SIZE(HDRP(ptr));
	
	PUT(HDRP(ptr), PACK(size, 0));
	PUT(FTRP(ptr), PACK(size, 0));
	coalesce(ptr);
}

/* coalesce -- Coalesce free block */
static void *coalesce(void *ptr)
{
   
	size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(ptr)));
	size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
	size_t size = GET_SIZE(HDRP(ptr));
	
	if (prev_alloc && next_alloc) {
   				/* Case 1 */
		return ptr;
	} else if (prev_alloc && !next_alloc) {
   		/* Case 2 */
		size += GET_SIZE(HDRP(NEXT_BLKP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值