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,这意味着空间利用率的占比更高。

相关知识

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

Lab7 Malloc Lab_第1张图片

碎片现象

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

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

内存分配器的实现

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

空闲块组织

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

一个简单的堆块形式如下:
Lab7 Malloc Lab_第2张图片

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

Lab7 Malloc Lab_第3张图片

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

放置已分配的块

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

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

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

带边界标记的合并

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

Lab7 Malloc Lab_第4张图片

合并的情况分为四种:

  1. 前面的块和后面都是已分配的,Nothing to do。
  2. 前面的块是已分配的,后面的块是空闲的。
  3. 前面的块是空闲的,而后面的块是已分配的。
  4. 前面的和后面的块都是空闲的。
    Lab7 Malloc Lab_第5张图片

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

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

使用隐式空闲链表

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

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

Lab7 Malloc Lab_第6张图片

这里的隐式空闲链表的结构包括序言快、若干个普通块(已分配/空闲)、以及一个结尾块,值得注意的是,添加序言块和结尾块实现是比较方便的,比如,在合并操作时,不需要进行边界的处理,结尾块是一个大小为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(ptr)));
		PUT(HDRP(ptr), PACK(size, 0));
		PUT(FTRP(ptr), PACK(size, 0));
	} else if (!prev_alloc && next_alloc) {		/* Case 3 */
		size += GET_SIZE(FTRP(PREV_BLKP(ptr)));
		PUT(FTRP(ptr), PACK(size, 0));
		PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
		ptr = PREV_BLKP(ptr);
	} else if (!prev_alloc && !next_alloc) {	/* Case 4 */
		size += GET_SIZE(HDRP(PREV_BLKP(ptr))) 
			+ GET_SIZE(FTRP(NEXT_BLKP(ptr)));
		PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
		PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 0));
		ptr = PREV_BLKP(ptr);
	}
	
	return ptr;
}

分配块(malloc、realloc)

mm_alloc函数首先会对请求的size进行调整,添加上头部和尾部块以及满足双字对齐要求的填充部分开销,然后使用find_fit函数寻找一个合适的空闲块,其中放置策略为首次适配,如果找到了合适的空闲块就调用place函数,否则,使用mm_sbrk函数向操作系统增大堆指针,扩大堆,然后再调用place函数。其中place函数会进行可选的分割,当剩余大小大于一个最小块大小,这里为8字节,那么就会进行分割操作。

void *mm_malloc(size_t size)
{
    size_t asize;		/* Adjusted block size */
    size_t extendsize;	/* Amount to extend heap if no fit */
   	char *bp;
   	
   	/* Ignore spurious requests */
   	if (size == 0)
   		return NULL;
   	
   	/* Adjust block size to include overhead and alignment reqs. */
   	if (size <= DSIZE)
   		asize = 2*DSIZE;
   	else 
   		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
   	
   	/* Search the free list for a fit */
   	if ((bp = find_fit(asize)) != NULL) {
   		place(bp, asize);
   		return bp;
   	}
   	
   	/* No fit found. Get more memory and place the block */
   	extendsize = MAX(asize, CHUNKSIZE);
   	if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
   		return NULL;
   	place(bp, asize);

   	return bp;
}

/* find_fit -- find the first fit free block */
static void *find_fit(size_t asize)
{
	void* p;

	for (p = heap_listp; GET_SIZE(HDRP(p)) > 0; p = NEXT_BLKP(p)) {
		if (!GET_ALLOC(HDRP(p)) && (asize <= GET_SIZE(HDRP(p))))
			return p;
	}
	
	return NULL;
}

/* place when remaining part size is greater than 2 word, divide it. */
static void place(void *bp, size_t asize)
{
	size_t size = GET_SIZE(HDRP(bp));
	if (size - asize >= 2*WSIZE) {
		PUT(HDRP(bp), PACK(asize, 1));
		PUT(FTRP(bp), PACK(asize, 1));
		bp = NEXT_BLKP(bp);
		PUT(HDRP(bp), PACK(size-asize, 0));
		PUT(FTRP(bp), PACK(size-asize, 0));
	} else {
		PUT(HDRP(bp), PACK(size, 1));
		PUT(FTRP(bp), PACK(size, 1));
	}
}

接下来就是mm_realloc的实现,这个实现是根据mm_realloc的语义结合这种隐式空闲链表的组织方式实现的,mm_realloc的实现考虑的因素还是比较多的:

  • 首先要对ptrsize进行判断
    • 如果ptr==NULL,调用mm_malloc(size)
    • 否则如果size==NULL,调用mm_free(ptr)
  • 到这里ptr!=NULL and size > 0,首先对size进行调整(添加头部尾部、填充部分等开销),然后判断asize和这个块的大小关系:
    • 如果asize=blockSize,Nothing to do;
    • 如果asize,调用place函数
    • 如果asize>blockSize,那么需要考虑下一个块是否已分配并且考虑其大小:
      • 如果下一个块为空闲块,并且两个块的大小sizesum>=asize,那么先设置当前块的大小为sizesum,然后调用place函数即可。
      • 如果下一个块为空闲块,但是块的大小sizesum或者下一个块为已分配块,那么需要调用find_fit函数寻找合适的空闲块。
        • 如果找得到,那么直接调用place函数;
        • 否则,向操作系统申请一个新的堆块,然后调用place函数;
        • 最后,使用memcpy将原来内存的数据拷贝过去。
      • 最后将oldptr释放掉即可。
void *mm_realloc(void *ptr, size_t size)
{
    void *oldptr = ptr;
    void *newptr;
	void *nextptr;

	size_t blockSize;
    size_t extendsize;
	size_t asize;	/* Adjusted block size */
	size_t sizesum;
	
	if (ptr == NULL) { 			/* If ptr == NULL, call mm_alloc(size) */
		return mm_malloc(size);
	} else if (size == 0) { 	/* If size == 0, call mm_free(size) */
		mm_free(ptr);
		return NULL;
	}

	/* Adjust block size to include overhead and alignment reqs. */
	if (size <= DSIZE)
   		asize = 2*DSIZE;
   	else 
   		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
	
	blockSize = GET_SIZE(HDRP(ptr));

	
	if (asize == blockSize) {						/* Case 1: asize == block size, nothing to do.  */
		return ptr;
	} else if (asize < blockSize) {					/* Case 2: asize < blockSize, */
		place(ptr, asize);
		return ptr;
	} else {										/* Case 3: asize > blockSize */
		/* Check next block */
		nextptr = NEXT_BLKP(ptr);
		sizesum = GET_SIZE(HDRP(nextptr))+blockSize;	/* Sum of this and next block size */
		if (!GET_ALLOC(HDRP(nextptr)) && sizesum >= asize) {	/* Next block is free and size is big enough. */
			/* Coalesce two block by set this block size be sizesum. */
			PUT(HDRP(ptr), PACK(sizesum, 0));
			place(ptr, asize);
			return ptr;
		} else {		/* Next block is allocated or size is not enough. */
			newptr = find_fit(asize);
			if (newptr == NULL) {
				extendsize = MAX(asize, CHUNKSIZE);
				if ((newptr = extend_heap(extendsize/WSIZE)) == NULL) {	/* Can not find a fit block, it must allocate memory from heap. */
					return NULL;
				}
			}
			place(newptr, asize);
			memcpy(newptr, oldptr, blockSize-2*WSIZE);
			mm_free(oldptr);
			return newptr;
		}
	}

}

测试结果

$ ./mdriver -v -t /home/struggle/CSAPP/malloclab-handout/traces/
Team Name:Bug Makers
Member 1 :Struggle:[email protected]
Using default tracefiles in /home/struggle/CSAPP/malloclab-handout/traces/
Measuring performance with gettimeofday().

Results for mm malloc:
trace  valid  util     ops      secs  Kops
 0       yes   99%    5694  0.006302   904
 1       yes   99%    5848  0.005826  1004
 2       yes   99%    6648  0.009767   681
 3       yes  100%    5380  0.007214   746
 4       yes   66%   14400  0.000087165517
 5       yes   92%    4800  0.005798   828
 6       yes   92%    4800  0.005205   922
 7       yes   55%   12000  0.133700    90
 8       yes   51%   24000  0.245958    98
 9       yes   80%   14401  0.000143100989
10       yes   46%   14401  0.000093155518
Total          80%  112372  0.420092   267

Perf index = 48 (util) + 18 (thru) = 66/100

从结果来看,至少这个简单的版本逻辑上是正确的,性能分为66,从Util和Kops看出,有些测试文件的空间利用率很高,有些比较低,这跟引用模式有关,吞吐率不高,可以看到某些测试文件中的Kops甚至不到100,有待提高。

使用显式空闲链表

隐式空闲链表比较简单,对于通用的分配器,隐式空闲链表是不适合的,一个更好的办法是将空闲块组织为某种形式的显式数据结构,堆可以组织成一个双向的空闲链表,在每个空闲块中,都包含一个predsucc指针。

Lab7 Malloc Lab_第7张图片

使用双向链表,使得首次适配的分配世家从块总数的线性时间减少到了空闲块数量的线性时间,但是,释放一个块的时间可以是线性的,也可能是一个常数,取决于所选择的空闲链表中块的排序策略。

  • 后进先出(LIFO)维护链表,将新释放的块放置在链表的开始处。使用LIFO顺序和首次适配的放置策略,分配器会最先检查最近使用过的块,这种情况下,释放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。
  • 按照地址顺序维护链表,其中链表中的每一个块的地址都小于它后继的地址。在这种情况下,释放一个块需要线性时间定位合适的前驱,平衡点在于,按照地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用率。

显式链表的缺点就是空闲块必须足够大,包含所需要的指针,以及头部和可能的脚部,提高了内部碎片的程度。

使用显式空闲链表实现的动态内存分配器,一些常用的宏定义如下,是原始版本的扩展,新增了获取和设置PREDSUCC的几个宏定义:

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

/* Get and Set pointer value by ptr at address p */
#define GET_PTR_VAL(p)	(*(unsigned long *)(p))
#define SET_PTR(p, ptr)	(*(unsigned long *)(p) = (unsigned long)(ptr))

/* Read and write pred and succ pointer at address p */
#define GET_PRED(p)	((char *)(*(unsigned long *)(p)))
#define GET_SUCC(p)	((char *)(*(unsigned long *)(p + DSIZE)))
#define SET_PRED(p, ptr)	(SET_PTR((char *)(p), ptr))
#define SET_SUCC(p, ptr)	(SET_PTR(((char *)(p)+(DSIZE)), ptr))

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

/* Free list pointer */
static char *free_list_headp;
static char *free_list_tailp;

init

显式空闲链表初始化,分配一个空的堆区,头部和尾部都要添加一个4字节的Padding,以满足8字节对齐的要求:

Lab7 Malloc Lab_第8张图片

分配一个初始堆区,大小为:CHUNKSIZE=4096bytes

Lab7 Malloc Lab_第9张图片

需要说明的是,使用的空闲链表是一个双向链表,包含一个free_list_headpfree_list_tailp节点,有了这两个节点,可以比较方便的对空闲链表进行插入修改等操作,虽然这两个节点位于空闲链表,但是他们确实已分配的块,在某些操作中需要进行特殊的考虑。

int mm_init(void)
{
	/* Create the initial empty heap(40 bytes) */
	if ((free_list_headp = mem_sbrk(4*WSIZE+3*DSIZE)) == (void *)-1) {
		return -1;
	}
	PUT(free_list_headp, PACK(0, 0));					/* Padding - for alignment 8 bytes */
	PUT(free_list_headp+WSIZE, PACK(24, 1));			/* Prologue header */
	free_list_headp += DSIZE;							/* free list head ptr */
	free_list_tailp = NEXT_BLKP(free_list_headp);		/* free list tail ptr */
	SET_PRED(free_list_headp, NULL);					/* Prologue pred */
	SET_SUCC(free_list_headp, free_list_tailp);			/* Prologue succ */
	PUT(free_list_headp+(2*DSIZE), PACK(24, 1));		/* Prologue footer */

	PUT(HDRP(free_list_tailp), PACK(0, 1));				/* Epilogue header */
	SET_PRED(free_list_tailp, free_list_headp);			/* Epilogue pred */
	PUT(free_list_tailp+DSIZE, PACK(0, 0));				/* Padding - for alignment 8 bytes */
	/* Extend the empty heap with a free block of CHUNKSIZE bytes */
	if (extend_heap(CHUNKSIZE/WSIZE) == NULL) {
		return -1;
	}
    
    return 0;
}

static void *extend_heap(size_t words)
{
	char *bp;
	char *ptr;
	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 */
	ptr = free_list_tailp;
	PUT(HDRP(ptr), PACK(size, 0));		/* Free block header */
	PUT(FTRP(ptr), PACK(size, 0));		/* Free block footer */
	free_list_tailp = NEXT_BLKP(ptr);	/* Update free list tailp */
	SET_SUCC(ptr, free_list_tailp);		/* Update free list */

	/* New epilogue block */
	PUT(HDRP(free_list_tailp), PACK(0, 1));
	SET_PRED(free_list_tailp, ptr);
	PUT(free_list_tailp+DSIZE, PACK(0, 0));
	/* Coalesce if the previous block was free */
	return coalesce(ptr);
	
}

释放和合并块(free)

释放一个块,为了让空闲块按照地址顺序排序,那么除了将这个块的头部和尾部已分配/空闲位设置为0之外,还需要将其插入空闲链表free_list的适当位置,使得它按照地址顺序排序,具体的做法是:在空闲链表中顺序查找,找到第一个空闲的块,这个块肯定在空闲链表里,然后设置相应的PREDSUCC值即可。

释放掉这个块后调用coalesce对这个块进行合并,合并的操作在常数时间内。

mm_free

void mm_free(void *ptr)
{
	char *p;
	size_t size = GET_SIZE(HDRP(ptr));

	/* Maintain free list in address order */
	for (p = GET_SUCC(free_list_headp); ; p = GET_SUCC(p)) {
		if (ptr < (void *)p) {		/* Find a suitable position- the first free block after this block. */
			PUT(HDRP(ptr), PACK(size, 0));
			PUT(FTRP(ptr), PACK(size, 0));
			SET_SUCC(ptr, p);
			SET_PRED(ptr, GET_PRED(p));
			/* Change previous and next free block */
			SET_SUCC(GET_PRED(p), ptr);
			SET_PRED(p, ptr);
			break;
		}
	}

	coalesce(ptr);
}

coalesce

/* 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));
	
	void *prevptr = PREV_BLKP(ptr);
	void *nextptr = NEXT_BLKP(ptr);
	if (prev_alloc && next_alloc) {				/* Case 1 - Nothing to do */
		return ptr;
	} else if (prev_alloc && !next_alloc) {		/* Case 2 */
		size += GET_SIZE(HDRP(nextptr));
		SET_SUCC(ptr, GET_SUCC(nextptr));
		SET_PRED(GET_SUCC(nextptr), ptr);
		PUT(HDRP(ptr), PACK(size, 0));
		PUT(FTRP(nextptr), PACK(size, 0));
	} else if (!prev_alloc && next_alloc) {		/* Case 3 */
		size += GET_SIZE(FTRP(prevptr));
		SET_SUCC(prevptr, GET_SUCC(ptr));
		SET_PRED(GET_SUCC(ptr), prevptr);
		PUT(HDRP(prevptr), PACK(size, 0));
		PUT(FTRP(ptr), PACK(size, 0));
		ptr = prevptr;
	} else if (!prev_alloc && !next_alloc) {	/* Case 4 */
		size += GET_SIZE(HDRP(prevptr)) 
			+ GET_SIZE(FTRP(nextptr));
		SET_SUCC(prevptr, GET_SUCC(nextptr));
		SET_PRED(GET_SUCC(nextptr), prevptr);
		PUT(HDRP(prevptr), PACK(size, 0));
		PUT(FTRP(nextptr), PACK(size, 0));
		ptr = prevptr;
	}
    
	return ptr;
}

合并操作和隐式空闲链表的类似,唯一有区别的是,就是还需要更改显式空闲链表的状态,使得相邻空闲块达到合并的效果,也就是相邻空闲块的整体在空闲链表中,没有分隔,总可以通过4行代码达到这种效果,每一个Case的代码类似,先修改显式空闲链表,然后再修改头部和尾部。

分配块(malloc、realloc)

仿照隐式空闲链表的写法,下面的mm_alloc实现是类似的,区别在于asize的设定,以及find_fit函数,place函数都可能需要修改空闲链表,涉及到指针的操作,容易出现BUG。

mm_malloc:设定asize,它的大小最小为24Bytes,如果size<16bytes,那么用最小块大小24Bytes代替,否则,需要只需要考虑头部尾部的开销,还需要进行双字对齐的额外填充开销。然后在空闲链表使用首次适配的方式顺序搜索,找到第一个适配的空闲块,调用place函数放置即可。

void *mm_malloc(size_t size)
{
    size_t asize;		/* Adjusted block size */
    size_t extendsize;	/* Amount to extend heap if no fit */
   	char *bp;

   	/* Ignore spurious requests */
   	if (size == 0)
   		return NULL;
   	
   	/* Adjust block size to include overhead and alignment reqs. */
	if (size <= 2*DSIZE) {
		asize = 3*DSIZE;
	} else {
		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
	}

   	/* Search the free list for a fit */
   	if ((bp = find_fit(asize)) != NULL) {
   		place(bp, asize);
   		return bp;
   	}
   	
   	/* No fit found. Get more memory and place the block */
   	extendsize = MAX(asize, CHUNKSIZE);
   	if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
   		return NULL;
   	place(bp, asize);
    
   	return bp; 	
}

find_fit:顺序遍历空闲链表,找到第一个适配的空闲块。

/* find_fit -- find the first fit free block in free list. */
static void *find_fit(size_t asize)
{
	void* p;

	for (p = GET_SUCC(free_list_headp); p != free_list_tailp; p = GET_SUCC(p)) {
		if (asize <= GET_SIZE(HDRP(p)))
			return p;
	}

	return NULL;
}

place:放置策略和初始版本是一致的:

  • 如果剩余部分大于24Bytes,那么选择分割。
    • 设置当前块的头部和尾部为大小asize,并设置已分配位;
    • 获取剩余部分块指针ptr,然后将原本位于free list 中的bp开始的那一块剔除,并且加入ptr指向的这一块空闲块。
  • 否则不进行分割,设置好当前块头部和尾部大小为size,并设置已分配位,然后将bp指向的块从free list剔除即可。
/* place when remaining part size is greater than 6 word, divide it. */
static void place(void *bp, size_t asize)
{
	size_t size = GET_SIZE(HDRP(bp));
	void *ptr;

	if (size - asize >= 3*DSIZE) {
		PUT(HDRP(bp), PACK(asize, 1));
		PUT(FTRP(bp), PACK(asize, 1));
		ptr = NEXT_BLKP(bp);
		/* Set next block - succ and pred */
		SET_SUCC(ptr, GET_SUCC(bp));
		SET_PRED(ptr, GET_PRED(bp));
		/* Change the previous and next free block state. */
		SET_SUCC(GET_PRED(ptr), ptr);
		SET_PRED(GET_SUCC(ptr), ptr);
		
		PUT(HDRP(ptr), PACK(size-asize, 0));
		PUT(FTRP(ptr), PACK(size-asize, 0));
		
	} else {
		PUT(HDRP(bp), PACK(size, 1));
		PUT(FTRP(bp), PACK(size, 1));

		/* Change the previous and next free block state */
		SET_SUCC(GET_PRED(bp), GET_SUCC(bp));
		SET_PRED(GET_SUCC(bp), GET_PRED(bp));
	}

}

realloc:和原来的版本差不多,区别在于如果newptr可以等于oldptr,跟place的操作类似,但不能直接调用place函数,因为place的实现是假设这个块是空闲块,来进行相应的操作的,对于显式空闲块的结构,place需要用到空闲块的PREDSUCC指针值,但是这里是已分配的块,没有PREDSUCC这两个指针,因此不能调用place,整个的操作是比较复杂的,代码也比较长。

void *mm_realloc(void *ptr, size_t size)
{
    void *oldptr = ptr;
    void *newptr;
	void *nextptr;
	void *pred;
	void *succ;
	char *p;

	size_t blockSize;
    size_t extendsize;
	size_t asize;	/* Adjusted block size */
	size_t sizesum;

	if (ptr == NULL) { 			/* If ptr == NULL, call mm_alloc(size) */
		return mm_malloc(size);
	} else if (size == 0) { 	/* If size == 0, call mm_free(size) */
		mm_free(ptr);
		return NULL;
	}

	/* Adjust block size to include overhead and alignment reqs. */
	if (size <= 2*DSIZE) {
		asize = 3*DSIZE;
	} else {
		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
	}

	blockSize = GET_SIZE(HDRP(ptr));

	
	if (asize == blockSize) {						/* Case 1: asize == block size, nothing to do.  */
		return ptr;
	} else if (asize < blockSize) {					/* Case 2: asize < blockSize, */
		if (blockSize-asize >= 3*DSIZE) {	
			/* Change this block header and footer */
			PUT(HDRP(ptr), PACK(asize, 1));
			PUT(FTRP(ptr), PACK(asize, 1));
			/* Change next block header and footer */
			nextptr = NEXT_BLKP(ptr);
			PUT(HDRP(nextptr), PACK(blockSize-asize, 0));
			PUT(FTRP(nextptr), PACK(blockSize-asize, 0));
			/* Find a suitable place in free list , insert into free list. */
			for (p = GET_SUCC(free_list_headp); ; p = GET_SUCC(p)) {
				if (nextptr < (void *)p) {
					pred = GET_PRED(p);
					succ = p;
					SET_PRED(nextptr, pred);
					SET_SUCC(nextptr, succ);
					SET_SUCC(pred, nextptr);
					SET_PRED(p, nextptr);
					break;
				}
			}
		} 
		/*  OR remaining block size is less than 24 bytes, cannot divide, nothing to do */
		return ptr;
	} else {										/* Case 3: asize > blockSize */
		/* Check next block */
		nextptr = NEXT_BLKP(ptr);
		sizesum = GET_SIZE(HDRP(nextptr))+blockSize;	/* Sum of this and next block size */
		if (!GET_ALLOC(HDRP(nextptr)) && sizesum >= asize) {	/* Next block is free and size is big enough. */
			/* Change free list */
			pred = GET_PRED(nextptr);
			succ = GET_SUCC(nextptr);
			if (sizesum-asize >= 3*DSIZE) {
				PUT(HDRP(ptr), PACK(asize, 1));
				PUT(FTRP(ptr), PACK(asize, 1));
				/* Set next block header, footer and pred, succ and insert it into free list. */
				nextptr = NEXT_BLKP(ptr);
				PUT(HDRP(nextptr), PACK(sizesum-asize, 0));
				PUT(FTRP(nextptr), PACK(sizesum-asize, 0));
				/* Insert into free list. */
				SET_PRED(nextptr, pred);
				SET_SUCC(nextptr, succ);
				SET_SUCC(pred, nextptr);
				SET_PRED(succ, nextptr);
			} else {
				PUT(HDRP(ptr), PACK(sizesum, 1));
				PUT(FTRP(ptr), PACK(sizesum, 1));
				/* delete next free block out of free list. */
				SET_SUCC(pred, succ);
				SET_PRED(succ, pred);
			}
			return ptr;
		} else {		/* Next block is allocated or size is not enough. */
			newptr = find_fit(asize);
			if (newptr == NULL) {
				extendsize = MAX(asize, CHUNKSIZE);
				if ((newptr = extend_heap(extendsize/WSIZE)) == NULL) {	/* Can not find a fit block, it must allocate memory from heap. */
					return NULL;
				}
			}
			place(newptr, asize);
			memcpy(newptr, oldptr, blockSize-2*WSIZE);
			mm_free(oldptr);
			return newptr;
		}
	}

}

测试结果

$ ./mdriver -v -t /home/struggle/CSAPP/malloclab-handout/traces/
Team Name:Bug Makers
Member 1 :Struggle:[email protected]
Using default tracefiles in /home/struggle/CSAPP/malloclab-handout/traces/
Measuring performance with gettimeofday().

Results for mm malloc:
trace  valid  util     ops      secs  Kops
 0       yes   99%    5694  0.000135 42147
 1       yes   99%    5848  0.000121 48491
 2       yes   99%    6648  0.000187 35646
 3       yes   99%    5380  0.000143 37517
 4       yes   66%   14400  0.000117123182
 5       yes   92%    4800  0.001902  2524
 6       yes   92%    4800  0.002291  2096
 7       yes   55%   12000  0.042181   284
 8       yes   51%   24000  0.138795   173
 9       yes   80%   14401  0.000149 96651
10       yes   46%   14401  0.000087165910
Total          80%  112372  0.186106   604

Perf index = 48 (util) + 40 (thru) = 88/100

对比原始的版本,使用显式空闲链表结构实现的动态内存分配器,其吞吐率上升了1倍,已经达到了吞吐率的最高得分,虽然与之而来的空间利用率降低了一点,但是吞吐率的提高给整体带来了更大优化效果,最终性能得分为:88。

使用分离适配链表

在前面使用显式空闲链表的分配器需要与空闲块数量呈线性关系来分配块,另一种减少分配世家的方法,称为分离存储(Segregated storage),就是维护多个空闲链表,其中每个链表中的块有大致相等的大小。一般的思路是将所有可能的块大小分成一些等价类,也叫做 大小类(size class),有很多种方式定义大小类,比如按照2的幂来划分块大小。

使用分离适配链表,分配器维护者一个空闲链表数组,每个空闲链表是和一个大小类相关联的,并且被组织呈某种类型的显式或隐式链表,每个链表包含潜在的大小不同的块,这些块的大小是大小类的成员。

为了分配一个块,必须确定请求的大小类,并且对适当的空闲链表做首次适配,查找一个合适的块,如果找到了,那么就(可选地)分割它,并将剩余的部分插入到适当的空闲链表中,如果找不到合适的块,那么就搜索下一个更大的大小类的空闲链表,如此重复,直到找到一个合适的块。如果空闲链表中没有一个合适的块,那么就向操作系统请求额外的堆内存,从这个新的堆内存中分配一个块,将剩余部分放置在适当的大小类当中。要释放一个块,我们执行合并,并将结果放置到相应的空闲链表中。

**下面是使用分离适配链表是实现思路:**确定大小类,使用显式的空闲链表结构,由于使用显式的空闲链表,那么块的最小块大小为24Bytes,因此大小类按这样的方式划分:{24 ~ 32}、{33 ~ 64},{65 ~ 128}、{129 ~ 256}、{257 ~ 512}、{513 ~ 1024}、{1025 ~ 2048}、{2049 ~ 4096}、{4097 ~ ∞ }。一共有9个大小的链表,可以分配在序言块,

两个操作双向链表的函数:remove、insert,只有几行代码,但是在实现的过程会经常用到。

/* Remove a node from a double linked list. */
static void remove_block(char *p)
{
	void *pred = GET_PRED(p);
	void *succ = GET_SUCC(p);

	SET_SUCC(pred, succ);
	SET_PRED(succ, pred);
}

/* Insert a noe into a double linked list (Head Insertion). */
static void insert_block(char *listp, char *p)
{
	SET_PRED(p, listp);
	SET_SUCC(p, GET_SUCC(listp));
	
	SET_SUCC(listp, p);
	SET_PRED(GET_SUCC(p), p);
}

init

根据所设计的堆的基本结构,可以写出初始化的代码,分配18个节点,每个节点24Bytes,分别用来做9个大小类空闲链表的头尾节点,这样做会比较方便,特别是在插入和删除的时候,除此之外,为了8字节对齐,还需要在堆的首部添加4字节的Padding,以及在尾部填充一个Epilogue block,它只有Header部分,接着使用一个循环,将头节点和尾节点连接起来即可,最后分配一个初始堆块,大小为4096 Bytes。

int mm_init(void)
{
	int i;
	char *p;
	char *q;

	/* Create the initial empty heap(584 bytes)--- 9 headp block, and 9 tailp block, 18 pointer array and padding 8 bytes. */
	if ((heap_listp = mem_sbrk(2*(9*DSIZE) + (3*DSIZE)*18 + 2*WSIZE)) == (void *)-1) {
		return -1;
	}

	PUT(heap_listp, PACK(0, 0));						/* Padding- for alignment 8 bytes */
	heap_listp += WSIZE;
	segregated_free_list_headp = (char **)heap_listp;	/* Get segregated free list headp array */
	heap_listp += 9*DSIZE;
	segregated_free_list_tailp = (char **)heap_listp;	/* Get segregated free list tailp array */

	heap_listp += (9*DSIZE+WSIZE);

	/* Set each free list headp and tailp */
	p = heap_listp;
	q = heap_listp + (9*(3*DSIZE));
	segregated_list_size = 9;
	for (i = 0; i < segregated_list_size; i++) {
		/* Set header and footer */
		PUT(HDRP(p), PACK(3*DSIZE, 1));
		PUT(FTRP(p), PACK(3*DSIZE, 1));
		PUT(HDRP(q), PACK(3*DSIZE, 1));
		PUT(FTRP(q), PACK(3*DSIZE, 1));
		/* Set pred and succ */
		SET_PRED(p, NULL);
		SET_SUCC(p, q);
		SET_PRED(q, p);
		SET_SUCC(q, NULL);
		/* Add p and q into segregated free list headp and tailp array */
		segregated_free_list_headp[i] = p;
		segregated_free_list_tailp[i] = q;
		/* Iterate p and q */
		p = NEXT_BLKP(p);
		q = NEXT_BLKP(q);
	}

	PUT(HDRP(heap_listp+(18*(3*DSIZE))), PACK(0, 1));		/* Epilogue Header */
	
	/* Extend the empty heap with a free block of CHUNKSIZE bytes */
	if (extend_heap(CHUNKSIZE/WSIZE) == NULL) {
		return -1;
	}

    return 0;
}

extend_heap:申请双字对齐的大小为size字节的内存空间,并且设置头部和尾部,以及一个新的Epilogue Block,最后将这个新的块插入对应大小的链表即可。

static void *extend_heap(size_t words)
{
	char *bp;
	char *listp;
	size_t size;
	int idx;

	/* 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));
	PUT(FTRP(bp), PACK(size, 0));

	PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));	/* New Epilogue Block */

	listp = get_free_list(size);	/* Get free list by the size. */
	insert(listp, bp);				/* Insert new free block into free list */

	/* Coalesce if the previous block was free */
	return coalesce(bp);
	
}

get_free_list:由于我们初始化的时候分配了一个指针数组,用来存放相应的空闲链表的头指针,这个函数用来根据块大小size来获取相应的链表。

/* Get the size class free list index by the given size. */
static int get_free_list_idx(size_t size)
{
	int idx;
	if (size <= 32) {
		idx = 0;
	} else if (size <= 64) {
		idx = 1;
	} else if (size <= 128) {
		idx = 2;
	} else if (size <= 256) {
		idx = 3;
	} else if (size <= 512) {
		idx = 4;
	} else if (size <= 1024) {
		idx = 5;
	} else if (size <= 2048) {
		idx = 6;
	} else if (size <= 4096) {
		idx = 7;
	} else {	/* size > 4096 */
		idx = 8;
	}
	//printf("idx=%d\n", idx);

	return idx;
}

释放和合并块(free)

free的操作很简单,先设置已分配位/空闲位为0,然后将其插入相应的大小类空闲链表,最后合并空闲块。

void mm_free(void *ptr)
{
	char *listp;
	size_t size = GET_SIZE(HDRP(ptr));

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

	/* Insert to listp */
	listp = segregated_free_list_headp[get_free_list_idx(size)];
	insert_block(listp, ptr);

	/* Coalesce if previous or next block is free. */
	coalesce(ptr);
}

Coalesce:合并的操作同样是4种情况,但是需要删除位于空闲链表相邻的空闲块节点,然后重新设置新的合并块的大小,并插入相应的空闲链表即可。

static void *coalesce(void *ptr)
{
	void *prevptr = PREV_BLKP(ptr);
	void *nextptr = NEXT_BLKP(ptr);
	size_t prev_alloc = GET_ALLOC(FTRP(prevptr));
	size_t next_alloc = GET_ALLOC(HDRP(nextptr));
	size_t size = GET_SIZE(HDRP(ptr));

	char *listp;
	if (prev_alloc && next_alloc) {				/* Case 1 - Nothing to do */
		return ptr;
	} else if (prev_alloc && !next_alloc) {		/* Case 2 */
		size += GET_SIZE(HDRP(nextptr));
		/* Remove next and current block in the free list. */
		remove_block(ptr);
		remove_block(nextptr);
	} else if (!prev_alloc && next_alloc) {		/* Case 3 */
		size += GET_SIZE(FTRP(prevptr));
		/* Remove previous and current block in the free list. */
		remove_block(prevptr);
		remove_block(ptr);
		ptr = prevptr;
	} else if (!prev_alloc && !next_alloc) {	/* Case 4 */
		size += GET_SIZE(HDRP(prevptr)) +
			GET_SIZE(FTRP(nextptr));
		/* Remove previous, current and next block in the free list. */
		remove_block(prevptr);
		remove_block(ptr);
		remove_block(nextptr);
		ptr = prevptr;
	}

	/* Set New header and footer */
	PUT(HDRP(ptr), PACK(size, 0));
	PUT(FTRP(ptr), PACK(size, 0));
	/* Insert new coalescing block into free list. */
	listp = segregated_free_list_headp[get_free_list_idx(size)];
	insert_block(listp, ptr);

	return ptr;
}

分配块(malloc、realloc)

malloc的函数实际上和显式空闲链表实现的malloc函数一模一样,区别在于它的find_fit以及place函数的行为。

void *mm_malloc(size_t size)
{
    size_t asize;		/* Adjusted block size */
    size_t extendsize;	/* Amount to extend heap if no fit */
   	char *bp;

   	/* Ignore spurious requests */
   	if (size == 0)
   		return NULL;
   	
   	/* Adjust block size to include overhead and alignment reqs. */
	if (size <= 2*DSIZE) {
		asize = 3*DSIZE;
	} else {
		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
	}

   	/* Search the free list for a fit */
   	if ((bp = find_fit(asize)) != NULL) {
   		place(bp, asize);
   		return bp;
   	}
   	
   	/* No fit found. Get more memory and place the block */
   	extendsize = MAX(asize, CHUNKSIZE);
   	if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
   		return NULL;
   	place(bp, asize);

   	return bp; 	

}

find_fit:首先根据asize获取对应的大小类空闲链表的下标,然后顺序查找,找到一个大小合适的块即可,如果在这个空闲链表搜索不到,那么就搜索下一个链表,直到找到一个合适的块为止,如果找不到,返回NULL即可。

static void *find_fit(size_t asize)
{
	//printf("find_fit(%u)--in\n", asize);
	void* p;
	int i;
	int idx = get_free_list_idx(asize);
	char *listp;
	char *endp;

	/* Search a big enough block */
	for (i = idx; i < segregated_list_size; i++) {
		listp = segregated_free_list_headp[i];
		endp = segregated_free_list_tailp[i];
		for (p = GET_SUCC(listp); p != endp; p = GET_SUCC(p)) {
			if (asize <= GET_SIZE(HDRP(p)))  {
				return p;
			}
		}
	}
    
	/* not fit */
	return NULL;
}

place:place函数放置一个块,并且将相应的空闲块从空闲链表中删除,并可选地分割空闲块。

static void place(void *bp, size_t asize)
{
	char *listp;
	size_t size = GET_SIZE(HDRP(bp));
	void *ptr;

	if (size - asize >= 3*DSIZE) {
		PUT(HDRP(bp), PACK(asize, 1));
		PUT(FTRP(bp), PACK(asize, 1));
		ptr = NEXT_BLKP(bp);
		PUT(HDRP(ptr), PACK(size-asize, 0));
		PUT(FTRP(ptr), PACK(size-asize, 0));

		/* Remove old block from free list */
		remove_block(bp);
		/* Insert new block into free list. */
		listp = segregated_free_list_headp[get_free_list_idx(size-asize)];
		insert_block(listp, ptr);
	} else {
		PUT(HDRP(bp), PACK(size, 1));
		PUT(FTRP(bp), PACK(size, 1));
		/* Remove this block from free list. */
		remove_block(bp);
	}
}

mm_realloc:realloc函数的逻辑和前面的版本是一样的,只是具有细微的差别。

void *mm_realloc(void *ptr, size_t size)
{
    void *oldptr = ptr;
    void *newptr;
	void *nextptr;
	char *listp;

	size_t blockSize;
    size_t extendsize;
	size_t asize;	/* Adjusted block size */
	size_t sizesum;

	if (ptr == NULL) { 			/* If ptr == NULL, call mm_alloc(size) */
		return mm_malloc(size);
	} else if (size == 0) { 	/* If size == 0, call mm_free(size) */
		mm_free(ptr);
		return NULL;
	}

	/* Adjust block size to include overhead and alignment reqs. */
	if (size <= 2*DSIZE) {
		asize = 3*DSIZE;
	} else {
		asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
	}

	blockSize = GET_SIZE(HDRP(ptr));

	
	if (asize == blockSize) {						/* Case 1: asize == block size, nothing to do.  */
		return ptr;
	} else if (asize < blockSize) {					/* Case 2: asize < blockSize, */
		if (blockSize-asize >= 3*DSIZE) {
			/* Change this block header and footer */
			PUT(HDRP(ptr), PACK(asize, 1));
			PUT(FTRP(ptr), PACK(asize, 1));
			/* Change next block header and footer */
			nextptr = NEXT_BLKP(ptr);
			PUT(HDRP(nextptr), PACK(blockSize-asize, 0));
			PUT(FTRP(nextptr), PACK(blockSize-asize, 0));
			
			/* insert next block into free list */
			listp = segregated_free_list_headp[get_free_list_idx(blockSize-asize)];
			insert_block(listp, nextptr);
		} 
		/*  OR remaining block size is less than 24 bytes, cannot divide, nothing to do */
		return ptr;
	} else {										/* Case 3: asize > blockSize */
		/* Check next block */
		nextptr = NEXT_BLKP(ptr);
		sizesum = GET_SIZE(HDRP(nextptr))+blockSize;	/* Sum of this and next block size */
		if (!GET_ALLOC(HDRP(nextptr)) && sizesum >= asize) {	/* Next block is free and size is big enough. */
			/* Remove next free block from free list. */
			remove_block(nextptr);
			if (sizesum-asize >= 3*DSIZE) {
				
				//printf("sizesum is big enough!==>OUT\n");
				PUT(HDRP(ptr), PACK(asize, 1));
				PUT(FTRP(ptr), PACK(asize, 1));
				/* Set next block header, footer and pred, succ and insert it into free list. */
				nextptr = NEXT_BLKP(ptr);
				PUT(HDRP(nextptr), PACK(sizesum-asize, 0));
				PUT(FTRP(nextptr), PACK(sizesum-asize, 0));
				listp = segregated_free_list_headp[get_free_list_idx(sizesum-asize)];
				insert_block(listp, nextptr);
			} else {
				PUT(HDRP(ptr), PACK(sizesum, 1));
				PUT(FTRP(ptr), PACK(sizesum, 1));
			}
			return ptr;
		} else {		/* Next block is allocated or size is not enough. */
			newptr = find_fit(asize);
			if (newptr == NULL) {
				extendsize = MAX(asize, CHUNKSIZE);
				if ((newptr = extend_heap(extendsize/WSIZE)) == NULL) {	/* Can not find a fit block, it must allocate memory from heap. */
					return NULL;
				}
			}
			place(newptr, asize);
			memcpy(newptr, oldptr, blockSize-2*WSIZE);
			mm_free(oldptr);
			return newptr;
		}
	}

}

测试结果

$ ./mdriver -v -t /home/struggle/CSAPP/malloclab-handout/traces/
Team Name:Bug Makers
Member 1 :Struggle:[email protected]
Using default tracefiles in /home/struggle/CSAPP/malloclab-handout/traces/
Measuring performance with gettimeofday().

Results for mm malloc:
trace  valid  util     ops      secs  Kops
 0       yes   97%    5694  0.000131 43333
 1       yes   97%    5848  0.000135 43254
 2       yes   98%    6648  0.000169 39454
 3       yes   99%    5380  0.000129 41803
 4       yes   64%   14400  0.000280 51410
 5       yes   89%    4800  0.000243 19721
 6       yes   86%    4800  0.000267 18011
 7       yes   55%   12000  0.000187 64205
 8       yes   51%   24000  0.000375 63966
 9       yes   33%   14401  0.038928   370
10       yes   30%   14401  0.000324 44434
Total          73%  112372  0.041168  2730

Perf index = 44 (util) + 40 (thru) = 84/100

最终的测试结果为:84分,不升反降,是因为使用分离适配链表+显式的空闲链表造成的内存开销会更大,由于第2个版本的吞吐率分数已经为40分,但是对比可知,使用分离适配的Kops为2730,而仅仅使用显式的空闲链表时仅仅为604,单从吞吐率来看,使用分离适配的方式吞吐率上升了很多,查阅config.h文件,可知,其定义的标准C库的平均吞吐率为600,而我们最优版本的吞吐率为2730,高了很多,说明标准C库的动态内存分配器使用了更复杂的方式,来提高空间利用率,从而造成吞吐率的降低。

由于性能计算公式,吞吐率占比40%,最高为40分,除此之外,发现最后两个测试文件(realloc)的空间利用率极低,说明这个引用模式很可能产生了大量的外部碎片,所以最终分数才会不升反降。

实验总结

Malloc Lab让我们手动编写实现一个基本的动态内存分配器,使用CSAPP书中介绍的几种方式,如最简单的隐式空闲链表,吞吐率相对来说会比较低;显式空闲链表,吞吐率大大提高;使用分离适配链表的方式,吞吐率极大提高。这个实验我尝试了三种实现的方式,实际上,标准C库使用的方式也是分离适配链表,理论上,使用这种方式实现的效果应该是最好的,但是最终结果,吞吐率确实提升极大,但是对于某些引用模式,空间利用率极低,表明产生了大量外部碎片,可能需要某种方式去避免产生如此大量的外部碎片。

总的来说,这个实验是比较有难度的,涉及到大量的指针操作,特别容易产生BUG,需要进行长时间的调试工作。

你可能感兴趣的:(CSAPP,c++,linux)