少女祈祷中...

所有代码只针对测试数据进行特别优化,可能不具有普适性

代码预览

显式空闲链表+分离适配+realloc_bal测试点优化+binary_bal测试点优化

宏定义

0
宏定义和书本基本相同,但增加了PRED和SUCC两个宏用作定位空闲块中的前驱指针和后继指针,前驱指针保存在空闲块首起4个字节,而后继指针保存在后4个字节

函数及全局变量声明

1
root指针数组用作分离适配器的空闲链表数组,维护着链表头
MAX_SIZE为root数组的大小
heap_listp为堆顶指针
根据2的幂划分块的大小:{08},{916},{1732},…,{20494096},{4097~∞}

mm_init函数

2
相比于书上代码增加了对root的初始化,同时改变初始扩展堆的大小(原为CHUNKSIZE/WSIZE,现改为115字节),经过测试,改后代码能比原代码增加5分

extend_heap函数

3
和书上代码相同

mm_malloc函数

4
和书上代码基本相同,但由于place函数由void类型改为void*类型,同时改变形参数量,所以对place函数有关代码做出了调整

mm_free函数

5
相比于书上代码增加了对于空闲块的前驱指针和后继指针的初始化操作

mm_realloc函数

6
ptr为空时直接调用mm_malloc函数,size为0时直接调用mm_free函数,否则
首先将输入的size进行8字节对齐,得到asize
接着判断ptr本身大小是否大于asize,如果小于等于则直接返回ptr指针(抛弃空闲区域),否则判断ptr后是否为空闲块且空闲块是最后一块,如果是则判断两者合并后是否放的下,如果放得下则直接更新ptr并返回,否则进行堆扩展并更新返回
如果不满足上述所有情况则进行常规mm_malloc并进行内存复制

judge函数

7
用于判断size大小的空闲块应该被放置在哪个空闲链表数组中

place函数

8
flag用于判断调用者来源于malloc还是realloc函数(realloc函数无需进行relink操作)
对于剩余部分大于2*DSIZE的情况,对asize进行判断,如果小于96(实际范围可以在80~110之间浮动,分数相同),则将空闲块放在右边,否则则将空闲块放在左边,放置的同时将空闲块的前驱和后继初始化并插入空闲链表中
对于剩余部分小于2*DSIZE的情况,抛弃剩余空闲区域
返回指针为分配块所对应指针

coalesce函数

9
和书上代码基本类似,只是在对应情况下加上relink对应空闲块
最后加上updatenode函数更新新的空闲块到对于空闲链表中

find_fit函数

10
从对应空闲链表数组中开始寻找,首次适配,如果当前数组中没有找到则跳入下一下标中继续寻找,如果所有空闲块都无法放入则返回NULL

relink函数

11
基础的空闲链表删除节点函数,最后对bp进行初始化

updatenode函数

12
基础的空闲链表(OrderedList)插入节点函数,链表为升序排列,分头部插入和非头部插入两种情况

优化原理

  • 采用显式空闲链表相对于隐式空闲链表,在插入的过程中只需遍历空闲链表而非整个堆,能大大加快速度
  • 采用分离适配,将将近大小的块放入一个链表中,类似于链式哈希表,能大大加快检索速度,同时由于优先遍历相近的块,所以相近大小的分配块会被分配到相近大小的空闲块中,提升内存利用率
  • 链表采用升序排列首次适配,所以每次都能适配到最接近大小的空闲块,即最佳适配,能在牺牲一小部分速度的同时大大提升内存利用率
  • 针对realloc_bal和realloc2_bal测试点进行优化,注意到测试点中对同一块区域进行反复realloc,size升序排列,同时内部穿插alloc和free,此时若对于realloc的块直接向后扩展堆,同时抛弃空闲块,则不会打乱堆中的顺序,使内存利用率上升
  • 针对binary_bal和binary2_bal测试点进行优化,注意到测试点中先反复alloc小块和大块,之后一次性free大块并alloc更大的块,如果不进行优化,则由于之后alloc的块并不能被插入到之前free的块中(大小不够),所以会造成空间的大量浪费,而选择将大块的空闲块放在左边,小块的空闲块放在右边,当free大块时,小块的空闲块,大块的空闲块和大块free的空间都能相合并,形成一个更大的空间并能容纳下之后alloc的块,能大大提升内存利用率
  • 其他优化:改变mm_init中初始堆的大小,经测试,当初始堆的大小为115字节时有最大内存利用率(仅对测试点进行优化,无普适性

实验过程及分析

详细注释请见完整代码

  • 使用书本上代码(隐式空闲链表)进行首次尝试
    13
    可以发现内存的利用率只有74%,而且速度也很慢
  • 随后尝试将隐式改为显式空闲链表(LIFO)
    14
    时间方面已经到达满分,之后需要对空间进行进一步优化
  • 考虑采用分离适配方法(LIFO),将块大小分解到131072大小
    15
    内存利用率只有2%的提升,这也在意料之中,因为分离适配主要提升速度
    首先观察到最后4个测试点的内存利用率非常低,首先考虑优化最后两个测试点(realloc_bal和realloc2_bal)
  • 通过观察测试点后选择修改realloc函数如下
    25
    26
    即首先判断asize是否和copySize相等,如果相等则直接返回ptr,如果小于则调用place函数对分配块进行重新分配,否则则调用coalesce2函数(和coalesce函数类似),对于原块的前后寻找是否存在空闲块,如果存在则对其进行合并并重新分配,否则则重新alloc并内存复制
  • 此外修改updatenode函数代码,使其变为升序排列,这样就可以配合哈希表达到不损耗太多速度的同时进行最优适配
    16
    此时内存利用率有了部分的提升
  • 接着考虑对后部空闲块直接扩展堆,即判断
1
(( !GET_ALLOC(HDRP(NEXT_BLKP(ptr)))&&!GET_SIZE(HDRP(NEXT_BLKP(NEXT_BLKP(ptr))))))
  • 在修改完代码并确认代码无误后却一直发生段错误
    17
    在尝试了许多办法后发现将CHUNKSIZE的大小缩减到256字节后就不再发生段错误,推测可能是代码之间仍然存在逻辑错误,或是由于测试点的特殊性导致段错误,而将CHUNKSIZE进行了修改后正好改变了分配块在内存中的排布顺序,从而消除了段错误
  • 同时最后的测试点有了较大幅度的提升,已经到 97%
    18
    随后尝试对binary_bal和binary2_bal测试点进行优化
    考虑按照块的大小选择place分配方式,即大块分在右边,而小块分在左边,分界线介于80~110字节之间
  • 但在修改完代码后发现
    19
    所有测试点均在第六行发生负载覆盖问题,通过查看测试点可以知道第一次malloc可以正常执行,但在进行第二次malloc后就覆盖掉了第一次malloc的内容
  • 调试发现
    20
    发现第一次将分配块(0xf697c118)的ALLOC置为1,但在 第二次malloc时ALLOC被隐式地修改回了0
    经过长时间的调试发现问题在于没有修改传入的bp指针,导致bp指针指向了错误的位置,而在之后malloc中影响到了之前的ALLOC
    所以将place函数从void类型修改为void *类型,将修改后的bp指针传出,并在malloc函数中更新bp指针即可消除负载覆盖的问题
  • 但这时又再次出现了段错误的问题
    21
    经过排查发现是coalesce2函数中判断块的前部是否存在空闲块和place函数发生了冲突
    简单起见,直接暴力删除coalesce2中对于flag==2的情况,同时将CHUNKSIZE修改回4096
  • 修改后最后两个测试点发生error
    22
  • 考虑coealesce2函数和其他许多函数可能发生了冲突,最后选择直接删除coalesce2整个函数
    23
    分数提升到了92分
    最后通过重整代码和修改CHUNSIZE大小和初始堆的大小,成功再次提升5分
  • 最终实验结果
    24

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/*
 * mm-naive.c - The fastest, least memory-efficient malloc package.
 * 
 * In this naive approach, a block is allocated by simply incrementing
 * the brk pointer.  A block is pure payload. There are no headers or
 * footers.  Blocks are never coalesced or reused. Realloc is
 * implemented directly using mm_malloc and mm_free.
 *
 * NOTE TO STUDENTS: Replace this header comment with your own header
 * comment that gives a high level description of your solution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

#include "mm.h"
#include "memlib.h"
/*********************************************************
 * NOTE TO STUDENTS: Before you do anything else, please
 * provide your team information in the following struct.
 ********************************************************/
team_t team = {
    /* Team name */
    "ECNU SE",
    /* First member's full name */
    "Jack Qiu",
    /* First member's email address */
    "2055272094@qq.com",
    /* Second member's full name (leave blank if none) */
    "",
    /* Second member's email address (leave blank if none) */
    ""};
/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8
/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~0x7)

#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
#define WSIZE 4
#define DSIZE 8
#define CHUNKSIZE 4096

#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 PRED(bp) ((char *)(bp))         //返回前驱结点
#define SUCC(bp) ((char *)(bp) + WSIZE) //返回后继结点

static void *extend_heap(size_t words);
static void *coalesce(void *bp);
static void relink(void *bp);
static void *find_fit(size_t size);
static voidplace(void *bp, size_t asize, int flag);
static int judge(int size);
static void updatenode(void *bp);

const int MAX_SIZE = 15;                //空闲链表数组大小
static void *heap_listp = NULL;
static unsigned int *root[15];          //空闲链表数组

static int judge(int size)
{
    //相比于采用log,考虑采用if-else语句提升速度
    int index = 0;
    if (size <= 8)          index = 3;  //由于8字节对齐,size不可能小于8
    else if (size <= 16)    index = 4;
    else if (size <= 32)    index = 5;
    else if (size <= 64)    index = 6;
    else if (size <= 128)   index = 7;
    else if (size <= 256)   index = 8;
    else if (size <= 512)   index = 9;
    else if (size <= 1024)  index = 10;
    else if (size <= 2048)  index = 11;
    else if (size <= 4096)  index = 12;
    else                    index  =13;
    return index;
}

/* 
 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
    if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1)
        return -1;
    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(01));
    for (int i = 0; i < MAX_SIZE; i++)
        root[i] = NULL;             //初始化空闲链表数组
    heap_listp += (4 * WSIZE);
    if (extend_heap(115) == NULL)   //对于测试点进行特别优化
        return -1;
    return 0;
}

static void *extend_heap(size_t words)
{
    char *bp;
    size_t size;
    size = (words % 2) ? (words + 1) * DSIZE : words * DSIZE;   //WSIZE修改为DSIZE
    if ((long)(bp = mem_sbrk(size)) == -1)
        return NULL;
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    PUT(PRED(bp), 0);       //前驱结点初始化
    PUT(SUCC(bp), 0);       //后继节点初始化
    PUT(HDRP(NEXT_BLKP(bp)), PACK(01));
    return coalesce(bp);
}

/* 
 * mm_malloc - Allocate a block by incrementing the brk pointer.
 *     Always allocate a block whose size is a multiple of the alignment.
 */
void *mm_malloc(size_t size)
{
    size_t asize;
    size_t extendsize;
    char *bp;
    if (size == 0)
        return NULL;
    if (size <= DSIZE)
        asize = 2 * (DSIZE);
    else
        asize = (DSIZE) * ((size + (DSIZE) + (DSIZE - 1)) / (DSIZE));
    if ((bp = find_fit(asize)) != NULL)
    {
        bp = place(bp, asize, 0);   //更新bp
        return bp;
    }
    extendsize = MAX(asize, CHUNKSIZE);
    if ((bp = extend_heap(extendsize / DSIZE)) == NULL)
        return NULL;
    bp = place(bp, asize, 0);       //更新bp
    return bp;
}

static void *find_fit(size_t size)
{
    for (int i = judge(size); i < MAX_SIZE; i++)    //若查找不到依次向下一个下标移动
    {
        void *bp = root[i];     //空闲链表内部查找
        while (bp != NULL)
        {
            if (size <= GET_SIZE(HDRP(bp)))
                return bp;
            bp = GET(SUCC(bp));
        }
    }
    return NULL;
}

static void *place(void *bp, size_t asize, int flag)
{
    size_t csize = GET_SIZE(HDRP(bp));
    if (!flag)          //malloc调用时需要relink,而realloc调用时不需要relink
        relink(bp);
    if ((csize - asize) >= (2 * DSIZE)) //空闲区域变为空闲块
    {
        //分割区间可在80字节~110字节之间浮动,对结果几乎无影响
        if (asize < 96//小于96字节
        {
            PUT(HDRP(bp), PACK(asize, 1));          //分配块置1
            PUT(FTRP(bp), PACK(asize, 1));
            bp = NEXT_BLKP(bp);
            PUT(HDRP(bp), PACK(csize - asize, 0));  //空闲块置0
            PUT(FTRP(bp), PACK(csize - asize, 0));
            PUT(PRED(bp), 0);   //前驱初始化
            PUT(SUCC(bp), 0);   //后继初始化
            updatenode(bp);     //将空闲块插入空闲链表中
            return PREV_BLKP(bp);   //返回分配块指针
        }
        else            //大于等于96字节
        {
            PUT(HDRP(bp), PACK(csize - asize, 0));  //空闲块置0
            PUT(FTRP(bp), PACK(csize - asize, 0));
            PUT(HDRP(NEXT_BLKP(bp)), PACK(asize, 1));   //分配块置1
            PUT(FTRP(NEXT_BLKP(bp)), PACK(asize, 1));
            PUT(PRED(bp), 0);   //前驱初始化
            PUT(SUCC(bp), 0);   //后继初始化
            updatenode(bp);     //将空闲块插入空闲链表中
            return NEXT_BLKP(bp);   //返回分配块指针
        }
    }
    else    //丢弃空闲区域
    {
        PUT(HDRP(bp), PACK(csize, 1));
        PUT(FTRP(bp), PACK(csize, 1));
        return bp;
    }
}
/*
 * mm_free - Freeing a block does nothing.
 */

void mm_free(void *ptr)
{
    size_t size = GET_SIZE(HDRP(ptr));
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));
    PUT(SUCC(ptr), 0);  //前驱初始化
    PUT(PRED(ptr), 0);  //后继初始化
    coalesce(ptr);
}

static void relink(void *bp)
{
    int index = judge(GET_SIZE(HDRP(bp)));
    void *before = GET(PRED(bp));
    void *after = GET(SUCC(bp));
    if (before == NULL)             //头部插入
    {
        if (after != NULL)
            PUT(PRED(after), 0);    //后块前驱置0
        root[index] = after;        //空闲数组更新头部
    }
    else
    {
        if (after != NULL)
            PUT(PRED(after), before);   //后块前驱更新
        PUT(SUCC(before), after);   //前块后驱更新
    }
    PUT(PRED(bp), 0);   //前驱初始化
    PUT(SUCC(bp), 0);   //后继初始化
}

static void *coalesce(void *bp)
{
    //所有情况一律选择先删除后插入节点
    size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size = GET_SIZE(HDRP(bp));
    if (prev_alloc && !next_alloc)  //Case 2
    {
        size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
        relink(NEXT_BLKP(bp));  //删除前面的空闲块
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    }
    else if (!prev_alloc && next_alloc) //Case 3 
    {
        size += GET_SIZE(HDRP(PREV_BLKP(bp)));
        relink(PREV_BLKP(bp));  //删除后面的空闲块
        PUT(FTRP(bp), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        bp = PREV_BLKP(bp);
    }
    else if (!prev_alloc && !next_alloc)    //Case 4 
    {
        size += GET_SIZE(FTRP(NEXT_BLKP(bp))) + GET_SIZE(HDRP(PREV_BLKP(bp)));
        relink(PREV_BLKP(bp));  //删除前面的空闲块
        relink(NEXT_BLKP(bp));  //删除后面的空闲块
        PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        bp = PREV_BLKP(bp);
    }
    updatenode(bp); //插入新的空闲块
    return bp;
}

static void updatenode(void *bp)
{
    void *now_root = root[judge(GET_SIZE(HDRP(bp)))];
    char *prevp = NULL;
    char *nextp = now_root;
    //升序排列
    while (nextp != NULL)
    {
        if (GET_SIZE(HDRP(nextp)) >= GET_SIZE(HDRP(bp)))
            break;  //确定插入位置
        prevp = nextp;
        nextp = GET(SUCC(nextp));
    }
    if (prevp == NULL)  //头部插入
    {
        root[judge(GET_SIZE(HDRP(bp)))] = bp;
        PUT(SUCC(bp), nextp);
        PUT(PRED(bp), 0);
        if (nextp != NULL)
            PUT(PRED(nextp), bp);
    }
    else                //中间插入
    {
        PUT(SUCC(prevp), bp);
        PUT(PRED(bp), prevp);
        PUT(SUCC(bp), nextp);
        if (nextp != NULL)
            PUT(PRED(nextp), bp);
    }
}

/*
 * mm_realloc - Implemented simply in terms of mm_malloc and mm_free
 */
void *mm_realloc(void *ptr, size_t size)
{
    if (ptr == NULL)    //ptr == NULL 直接mm_malloc
        return mm_malloc(size);
    if (size == 0)      //size == 0 直接free
    {
        mm_free(ptr);
        return NULL;
    }
    size_t asize;
    size_t copySize = GET_SIZE(HDRP(ptr));
    void *bp;
    if (size <= DSIZE)  //8字节对齐
        asize = 2 * (DSIZE);
    else
        asize = (DSIZE) * ((size + (DSIZE) + (DSIZE - 1)) / (DSIZE));
    if (copySize >= asize)  //小于原ptr大小抛弃空闲区域直接返回
        return ptr; 
    if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) && !GET_SIZE(HDRP(NEXT_BLKP(NEXT_BLKP(ptr)))))
    {
        //判断后块是否为空闲块且空闲块是否为最后一块
        size_t new_size = GET_SIZE(HDRP(ptr)) + GET_SIZE(HDRP(NEXT_BLKP(ptr)));
        if (new_size < asize)   //大小不够需要进行扩展堆
        {
            if (extend_heap(MAX(asize - new_size, CHUNKSIZE) == NULL))
                return NULL;    //扩展失败
            new_size += MAX(asize - new_size, CHUNKSIZE);   //更新大小
        }
        relink(NEXT_BLKP(ptr)); //删除空闲块
        PUT(HDRP(ptr), PACK(new_size, 1));  //更新原块大小
        PUT(FTRP(ptr), PACK(new_size, 1));
        return ptr;
    }
    //上述条件均不满足时选择原始方法
    bp = mm_malloc(size);
    memcpy(bp, ptr, size);
    mm_free(ptr);
    return bp;
}