写在前言:这个实验的来源是CSAPP官网:CSAPP Labs ,如果感兴趣的话,可以点击这个链接去下载。实验中的10个traces文件是没有附加的,可以点击这个:traces file 自行下载。
Malloc Lab实验要求我们实现一个动态内存分配器(Dynamic Memory Allocator),要求我们实现与标准C库中的malloc
、free
、realloc
具有相同功能的函数,可以自行定义块的空间结构。
唯一需要修改的文件是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_malloc
、mm_realloc
得到的ptr
),如果已经被释放,Nothing to do。mm_realloc
:返回一个已分配的块,其有效载荷payload
的大小至少为size
字节,并且有如下的限制:
ptr
为 NULL
,那么等价于调用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⋯,Rn−1
如果一个应用程序请求一个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=Hkmaxi≤kPi,越接近1表示空间利用率越高。
其次,吞吐率,表示单位时间内完成的请求个数,比如,如果分配器1秒内完成了500个分配请求和500个释放请求,那么它的吞吐率就是每秒1000次操作。
总的来说,分配器的目标就是在整个序列中使得峰值利用率 U n − 1 U_{n-1} Un−1最大化,并且峰值利用率和吞吐率是互相牵制的,找到一个合适的折中就显得很重要。
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+(1−w)min(1,TlibcT)
其中, w = 0.6 w=0.6 w=0.6,这意味着空间利用率的占比更高。
下图所示为用户内存空间的结构,动态内存分配器管理的是堆区。
为了提高分配器的性能,首先就要提高内存的空间利用率,需要理解造成空间利用率低的原因:碎片现象(Fragmentation),分为内部碎片和外部碎片。
任何实际的分配器都需要一些数据结构,允许它来区别块的边界一级区别已分配块和空闲块。
上述块的结构包括一个字的头部、有效载荷以及用于对齐而进行的填充组成的。
这种块结构间接地形成了一种链表结构,叫隐式空闲链表,它的结构是比较简单的。
当一个应用请求一个k字节的块,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块,分配器执行这种搜索的方式由 放置策略 确定,常见的策略:首次适配,下一次适配,最佳适配
实际上,下一次适配比首次适配运行明显要快一些,但是内存利用率比首次适配利用率低很多,在简单的空闲链表组织结构中,比如隐式空闲链表中,使用最佳适配的缺点是它要求对堆进行彻底的搜索。
Knuth提出了一种技术叫边界标记,允许在常数时间内继续块合并操作。
合并的情况分为四种:
边界标记的概念很简单,对于不同类型的分配器和空闲链表的组织都是通用的,然而,它也存在一个潜在的缺陷,要求每个块都保持一个头部和脚部,如果应用请求很多小块时,会产生显著的内存开销。
对这个方法进行优化,可以使得已分配的块中不再需要脚部,只有在前面的块是空闲的时候才需要有脚部,如果把前面块的已分配/空闲位存放在当前块中多出来的低位中,那么已分配块就不需要脚部了,这样就可以讲个多出来的空间用作有效载荷了,需要注意的是,空闲块仍然需要脚部。
第一种实现方式就是书本上给出的简单分配器实现,使用的块结构为未优化的带边界标记的堆块形式。
但是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;
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);
}
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;
}
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
的实现考虑的因素还是比较多的:
ptr
和size
进行判断
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,有待提高。
隐式空闲链表比较简单,对于通用的分配器,隐式空闲链表是不适合的,一个更好的办法是将空闲块组织为某种形式的显式数据结构,堆可以组织成一个双向的空闲链表,在每个空闲块中,都包含一个pred
和succ
指针。
使用双向链表,使得首次适配的分配世家从块总数的线性时间减少到了空闲块数量的线性时间,但是,释放一个块的时间可以是线性的,也可能是一个常数,取决于所选择的空闲链表中块的排序策略。
显式链表的缺点就是空闲块必须足够大,包含所需要的指针,以及头部和可能的脚部,提高了内部碎片的程度。
使用显式空闲链表实现的动态内存分配器,一些常用的宏定义如下,是原始版本的扩展,新增了获取和设置PRED
、SUCC
的几个宏定义:
/* 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;
显式空闲链表初始化,分配一个空的堆区,头部和尾部都要添加一个4字节的Padding,以满足8字节对齐的要求:
分配一个初始堆区,大小为:CHUNKSIZE=4096bytes
需要说明的是,使用的空闲链表是一个双向链表,包含一个free_list_headp
和free_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);
}
释放一个块,为了让空闲块按照地址顺序排序,那么除了将这个块的头部和尾部已分配/空闲位设置为0之外,还需要将其插入空闲链表free_list
的适当位置,使得它按照地址顺序排序,具体的做法是:在空闲链表中顺序查找,找到第一个空闲的块,这个块肯定在空闲链表里,然后设置相应的PRED
和SUCC
值即可。
释放掉这个块后调用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的代码类似,先修改显式空闲链表,然后再修改头部和尾部。
仿照隐式空闲链表的写法,下面的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
:放置策略和初始版本是一致的:
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
需要用到空闲块的PRED
、SUCC
指针值,但是这里是已分配的块,没有PRED
、SUCC
这两个指针,因此不能调用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);
}
根据所设计的堆的基本结构,可以写出初始化的代码,分配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
的操作很简单,先设置已分配位/空闲位为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
的函数实际上和显式空闲链表实现的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,需要进行长时间的调试工作。