CSAPP(CMU 15-213):Lab6 Malloclab详解
# 前言
本系列文章意在记录答主学习CSAPP Lab的过程,也旨在可以帮助后人一二,欢迎大家指正!
tips:本lab主要是为了体验应用程序如何使用和管理虚拟内存,写一个动态存储分配器(dynamic storage allocator)
目标:使用不同方法在内存利用率(memory utilization)和吞吐率(throughput)之中达到trade-off,逐步优化。
Handout
要求
修改mm.c
文件,其中包括四个函数:
int mm_init(void);
初始化堆空间,错误返回-1
,否则返回0
。void *mm_malloc(size_t size);
返回指向size
字节有效载荷的指针,其中堆块保持为8字节
对齐。void mm_free(void *prt);
释放由ptr
所指向堆块的空间。void *mm_realloc(void *ptr, size_t size);
尝试重新调整之前调用malloc
或calloc
所分配的ptr
所指向的内存块的大小,size
为新的内存空间的大小。- 描述:
- 如果
prt
是NULL
,则重新分配内存空间,与mm_malloc(size)
等价。 - 如果
size
是NULL
,则释放该内存空间,与mm_free(ptr)
等价。 - 新内存块的地址与旧内存块的地址可能相同,也可能不同,取决于分割策略,需求大小以及原内存块的内部碎片。
- 新内存块与旧内存块内容相同的大小取决于新旧内存块大小的最小值。
堆的一致性检查器(Heap Consistency Checker)
能够辅助检查堆的一致性,做为debugg
的工具。
功能
- 在空闲链表中的每一块是否都是空闲的?
- 是否存在一些连续的空闲块而没有合并?
- 每一个空闲块是否都在空闲链表中?
- 在空闲链表中的指针是否指向有效的空闲块?
- 已分配的块是否存在重叠的现象?
- 堆块中的指针是否指向有效的堆地址????
Support Routines
memlib.c
void *mem_sbrk(int incr);
按incr
个字节来扩充堆,其中incr
为正数,返回新区域第一个字节的地址。void *mem_heap_lo(void);
返回指向堆第一个字节的空指针。void *mem_heap_hi(void);
返回指向堆最后一个字节的空指针。size_t mem_heapsize(void);
返回当前堆的总大小(以字节为单位)。size_t mem_pagesize(void);
返回系统页桢的大小(linux
系统中为4K
)。
Programming Rules
- 不可改变
mm.c
中的函数接口。 - 不可以调用任何与内存管理相关的库函数。(
malloc
,calloc
,free
,realloc
,sbrk
,brk
等) - 不可以在
mm.c
中定义任何聚合的变量类型(array,structs,trees,lists等)
Hints
在最初时可以先使用简单的文件进行测试(eg.
short1,2-bal.rep
)unix> mdriver -V -f short1-bal.rep
理解书中基于隐式空闲列表实现的每一行代码。
在C预处理器宏中封装指针算法。(通过写宏可以降低指针运算的复杂度)
使用一些性能分析器。(eg.
gprof
)
准备工作
trace文件
在阅读官方handout过程中发现所给的文件中并没有几个trace
,就找寻了下,大家可以从下方的链接中下载,放入目录文档中。
Lab 6 Malloc Lab 完整的 12 traces Github 下载链接
同时在config.h
文件中将以下修改为自己的路径。
#define TRACEDIR "/traces/"
运行测试程序./mdriver -V
输出如下:
Team Name:ateam
Member 1 :Harry Bovik:bovik@cs.cmu.edu
Using default tracefiles in /mnt/hgfs/CMU15-213/lab/6.malloclab-handout/traces/
Measuring performance with gettimeofday().Testing mm malloc
Reading tracefile: amptjp-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: cccp-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: cp-decl-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: expr-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: coalescing-bal.rep
ERROR: mem_sbrk failed. Ran out of memory...
Checking mm_malloc for correctness, ERROR [trace 4, line 7673]: mm_malloc failed.
Reading tracefile: random-bal.rep
ERROR: mem_sbrk failed. Ran out of memory...
Checking mm_malloc for correctness, ERROR [trace 5, line 1662]: mm_malloc failed.
Reading tracefile: random2-bal.rep
ERROR: mem_sbrk failed. Ran out of memory...
Checking mm_malloc for correctness, ERROR [trace 6, line 1780]: mm_malloc failed.
Reading tracefile: binary-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: binary2-bal.rep
Checking mm_malloc for correctness, efficiency, and performance.
Reading tracefile: realloc-bal.rep
ERROR: mem_sbrk failed. Ran out of memory...
Checking mm_malloc for correctness, ERROR [trace 9, line 1705]: mm_realloc failed.
Reading tracefile: realloc2-bal.rep
ERROR: mem_sbrk failed. Ran out of memory...
Checking mm_malloc for correctness, ERROR [trace 10, line 6562]: mm_realloc failed.Results for mm malloc:
trace valid util ops secs Kops0 yes 23% 5694 0.0000232464941 yes 19% 5848 0.0000202938692 yes 30% 6648 0.0000242724593 yes 40% 5380 0.0000311763934 no - - - -5 no - - - -6 no - - - -7 yes 55% 12000 0.0000412905578 yes 51% 24000 0.0000793034139 no - - - -
10 no - - - -
Total - - - -Terminated with 5 errors
这样才算是将所有的准备工作都弄好啦。
GDB调试
在Makefile
文件中将
CC = gcc
CFLAGS = -Wall -O2 -m32
修改为
CC = gcc -g # 增加调试信息
CFLAGS = -Wall -O0 -m32 # 降低优化等级,以便于调试
Implementation
原生方法(naive method)
思想
最简单的分配器会把堆组织成一个大的字节数组,还有一个指针p,初始指向这个数组的第一个字节。为了分配 size 个字节,malloc 将p的当前值保存在栈里,将p增加 size,并将p的旧值返回到调用函数。free 只是简单地返回到调用函数,而不做其他事情。
优缺点:
- 吞吐率会极好(
malloc
和free
只执行很少的指令) - 内存利用率极差(因为分配器从不重复使用任何块)
函数设计原则
int mm_init(void);
- 无需操作
void *mm_malloc(size_t size);
- 在堆后直接创建空间存放
size
字节有效载荷和包含结点大小的头(注意对齐8字节)。
- 在堆后直接创建空间存放
void mm_free(void *prt);
- 无需释放空闲内存块。
void *mm_realloc(void *ptr, size_t size);
- 直接将创建新内存空间,无需管理原空间。
代码
/** 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 */"ateam",/* First member's full name */"Harry Bovik",/* First member's email address */"bovik@cs.cmu.edu",/* 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 */ // 对ALIGNMENT倍数上取整的计算
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)#define SIZE_T_SIZE (ALIGN(sizeof(size_t))) //将保存大小的部分也对齐是为了访问存储数据块的地址对齐,因为地址对齐的是有效载荷处,而不是块开头处(开关存放了此块的有效载荷大小)/* * mm_init - initialize the malloc package.*/
int mm_init(void)
{return 0;
}/* * 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)
{int newsize = ALIGN(size + SIZE_T_SIZE); void *p = mem_sbrk(newsize); //p指向新的分配块的第一个字节if (p == (void *)-1) //分配堆空间错误return NULL;else {*(size_t *)p = size; //有效载荷大小为sizereturn (void *)((char *)p + SIZE_T_SIZE);}
}/** mm_free - Freeing a block does nothing.*/
void mm_free(void *ptr)
{}/** mm_realloc - Implemented simply in terms of mm_malloc and mm_free*/
//无脑~直接在现有的堆后创建新空间
void *mm_realloc(void *ptr, size_t size)
{void *oldptr = ptr;void *newptr;size_t copySize; //旧内存块的大小newptr = mm_malloc(size);if (newptr == NULL)return NULL;copySize = *(size_t *)((char *)oldptr - SIZE_T_SIZE);if (size < copySize)copySize = size;memcpy(newptr, oldptr, copySize); //复制copeSize大小的数据至newptr中mm_free(oldptr);return newptr;
}
隐式空闲链表
思想
- 空闲块组织:隐式空闲链表
- 放置:首次适配(first fit) / 下一次适配(next fit)
- 分割:分割空闲块的前部使用。
- 合并:立即合并(immediate coalescing)
优缺点:
- 分配内存块 cost :线性的 cost
- 释放内存块 cost :与合并时间相加为常数时间
- 内存 cost :依赖于放置策略:fist-fit / next-fit / best-fit
函数设计原则
int mm_init(void);
- 初始化对齐块,序言块,结尾块,并扩充堆块大小。
void *mm_malloc(size_t size);
- 寻求合适的空闲块并分配,其间调用其他函数完成。
void mm_free(void *prt);
- 修改内存块的分配位并合并内存块。
void *mm_realloc(void *ptr, size_t size);
- 按要求分配内存块。
static void *extend_heap(size_t size);
- 扩充堆空间,并返回指向结尾扩充后的空闲块的指针
static void *find_fit(size_t size);
- 首次适配(first-fit)
static void place(char *bp, size_t size);
- 负责在空闲块内放置数据,使其分割为分配块和空闲块,注意最小块的限制。
static void *coalesce(void *bp);
- 合并空闲块,四种情况,返回指向该空闲块的指针。
static void mm_printblock(int verbose, const char* func);
- 打印输出整个堆块的模型,方便DEBUG
代码以及跑分
代码mm.c
/** 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 */"ateam",/* First member's full name */"Harry Bovik",/* First member's email address */"bovik@cs.cmu.edu",/* Second member's full name (leave blank if none) */"",/* Second member's email address (leave blank if none) */""
};#define VERBOSE 0
#ifdef DEBUG
#define VERBOSE 1
#endif/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8/* rounds up to the nearest multiple of ALIGNMENT */ // 对 ALIGNMENT 倍数上取整的计算
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))/* 自定义的宏,有便于操作常量和指针运算 */
#define WSIZE 4 //字、脚部或头部的大小(字节)
#define DSIZE 8 //双字大小(字节)
#define CHUNKSIZE (1<<12) //扩展堆时的默认大小
#define MINBLOCK (DSIZE + 2*WSIZE)#define MAX(x, y) ((x) > (y) ? (x) : (y))#define PACK(size, alloc) ((size) | (alloc)) //将 size 和 allocated bit 合并为一个字#define GET(p) (*(unsigned int *)(p)) //读地址p处的一个字
#define PUT(p, val) (*(unsigned int *)(p) = (val)) //向地址p处写一个字#define GET_SIZE(p) (GET(p) & ~0x07) //得到地址p处的 size
#define GET_ALLOC(p) (GET(p) & 0x1) //得到地址p处的 allocated bit
//block point --> bp指向有效载荷块指针
#define HDRP(bp) ((char*)(bp) - WSIZE) //获得头部的地址
#define FTRP(bp) ((char*)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) //获得脚部的地址, 与宏定义HDRP有耦合#define NEXT_BLKP(bp) ((char*)(bp) + GET_SIZE((char*)(bp) - WSIZE)) //计算后块的地址
#define PREV_BLKP(bp) ((char*)(bp) - GET_SIZE((char*)(bp) - DSIZE)) //计算前块的地址static void* heap_listp; //指向序言块
/* private functions */
static void *extend_heap(size_t size); //拓展堆块
static void *find_fit(size_t size); //寻找空闲块
static void place(char *bp, size_t size); //分割空闲块
static void *coalesce(void *bp); //合并空闲块
//check
/*
static void mm_check(int verbose, const char* func); //heap consistency checker
static void mm_checkblock(int verbose, const char* func, void *bp);
static int mm_checkheap(int verbose, const char* func);
*/
static void mm_printblock(int verbose, const char* func);/* * mm_init - initialize the malloc package.*/
//设立序言块、结尾块,以及序言块前的对齐块(4B),总共需要4个4B的空间
int mm_init(void)
{if ((heap_listp = mem_sbrk(4*WSIZE)) == (void*)-1) return -1;PUT(heap_listp, 0); //堆起绐位置的对齐块,使bp对齐8字节PUT(heap_listp + 1*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp + 2*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp, PACK(0, 1)); //结尾块heap_listp += (2*WSIZE); //小技巧:使heap_listp指向下一块, 即两个序主块中间if (extend_heap(CHUNKSIZE) == NULL) //拓展堆块return -1;mm_printblock(VERBOSE, __func__);return 0;
}static void *extend_heap(size_t size) {size_t asize; void *bp;asize = ALIGN(size);//printf("extend %d\n", asize);if ((long)(bp = mem_sbrk(asize)) == -1)return NULL;PUT(HDRP(bp), PACK(asize, 0)); //HDRP(bp)指向原结尾块PUT(FTRP(bp), PACK(asize, 0)); PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); //新结尾块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; //ajusted sizesize_t extendsize; //若无适配块则拓展堆的大小void *bp = NULL;if (size == 0) //无效的申请return NULL;asize = ALIGN(size + 2*WSIZE);if ((bp = find_fit(asize)) != NULL) {place((char *)bp, asize);mm_printblock(VERBOSE, __func__);return bp;}//无足够空间的空闲块用来分配extendsize = MAX(asize, CHUNKSIZE);if ((bp = extend_heap(extendsize)) == NULL) {return NULL;} place(bp, asize);mm_printblock(VERBOSE, __func__);return bp;
}//放置策略搜索 首次适配搜索
static void *find_fit(size_t size) { void *curbp;for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {if (!GET_ALLOC(HDRP(curbp)) && (GET_SIZE(HDRP(curbp)) >= size)) return curbp;}return NULL; //未适配
} //分割空闲块
static void place(char *bp, size_t asize) { //注意最小块的限制(16B == DSIZE + 2*WSIZE == MINBLOCK)size_t total_size = GET_SIZE(HDRP(bp));size_t remainder_size = total_size - asize;if (remainder_size >= MINBLOCK) {PUT(HDRP(bp), PACK(asize, 1));PUT(FTRP(bp), PACK(asize, 1));bp = NEXT_BLKP(bp);PUT(HDRP(bp), PACK(remainder_size, 0));PUT(FTRP(bp), PACK(remainder_size, 0));} else { //没有已分配块或空闲块可以比最小块更小PUT(HDRP(bp), PACK(total_size, 1));PUT(FTRP(bp), PACK(total_size, 1));}
}/** 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));coalesce(ptr);mm_printblock(VERBOSE, __func__);
}
/*
* coalesce - 合并内存块
*/
static void *coalesce(void *bp) { int pre_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));int post_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));size_t size = GET_SIZE(HDRP(bp));if (pre_alloc && post_alloc) {return bp;} else if (pre_alloc && !post_alloc) { //与后块合并size += GET_SIZE(HDRP(NEXT_BLKP(bp)));} else if (!pre_alloc && post_alloc) { //与前块合并size += GET_SIZE(HDRP(PREV_BLKP(bp)));bp = PREV_BLKP(bp);} else { //前后块都合并size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));bp = PREV_BLKP(bp);}PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0)); //FTRP()与GET_SIZE()有耦合,故此时所用的SIZE已经改变return bp;
}/** mm_realloc - Implemented simply in terms of mm_malloc and mm_free*/
void *mm_realloc(void *ptr, size_t size)
{size_t old_size, new_size, extendsize;void *old_ptr, *new_ptr;if (ptr == NULL) {return mm_malloc(size);}if (size == 0) {mm_free(ptr);return NULL;}new_size = ALIGN(size + 2*WSIZE);old_size = GET_SIZE(HDRP(ptr));old_ptr = ptr;if (old_size >= new_size) {if (old_size - new_size >= MINBLOCK) { //分割内存块place(old_ptr, new_size);mm_printblock(VERBOSE, __func__);return old_ptr;} else { //剩余块小于最小块大小,不分割mm_printblock(VERBOSE, __func__);return old_ptr;}} else { //释放原内存块,寻找新内存块if ((new_ptr = find_fit(new_size)) == NULL) { //无合适内存块extendsize = MAX(new_size, CHUNKSIZE);if ((new_ptr = extend_heap(extendsize)) == NULL) //拓展堆空间return NULL;}place(new_ptr, new_size);memcpy(new_ptr, old_ptr, old_size - 2*WSIZE);mm_free(old_ptr);mm_printblock(VERBOSE, __func__);return new_ptr;}
}static void mm_printblock(int verbose, const char* func) {if (!verbose) return;char *curbp;printf("\n=========================== %s ===========================\n" ,func);for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {printf("address = %p\n", curbp);printf("hsize = %d, fsize = %d\n", GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)));printf("halloc = %d, falloc = %d\n", GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));printf("\n");}//epilogue blocksprintf("address = %p\n", curbp);printf("hsize = %d\n", GET_SIZE(HDRP(curbp)));printf("halloc = %d\n", GET_ALLOC(HDRP(curbp)));printf("=========================== %s ===========================\n" ,func);
}/*
static void mm_check(int verbose, const char* func) {if (!verbose) return;if (mm_checkheap(verbose, func)) {void *curbp;for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {mm_checkblock(verbose, func, curbp);}}
}static void mm_checkblock(int verbose, const char* func, void* bp) {if (!verbose) return;if (GET(HDRP(bp)) != GET(FTRP(bp))) {printf("\n=========================== %s ===========================\n" ,func);printf("Error: %p's Header and footer are not match.\n", bp);printf("hsize = %d, fsize = %d\n", GET_SIZE(HDRP(bp)), GET_SIZE(FTRP(bp)));printf("halloc = %d, falloc = %d\n", GET_ALLOC(HDRP(bp)), GET_ALLOC(FTRP(bp)));printf("next_head_alloc = %d, next_footer_alloc = %d\n", GET_ALLOC(HDRP(NEXT_BLKP(bp))), GET_ALLOC(FTRP(NEXT_BLKP(bp))));printf("=========================== %s ===========================\n" ,func);}if ((int)bp % ALIGNMENT != 0) printf("Error: %p's Payload area is not aligned.\n", bp);if (GET_SIZE(HDRP(bp)) % ALIGNMENT != 0)printf("Error: %p payload size is not doubleword aligned.\n", bp);
}static int mm_checkheap(int verbose, const char* func) {char *endp = (char *)mem_heap_hi()+1;char *curbp;//check prologue blocksif (GET(HDRP(heap_listp)) != GET(FTRP(heap_listp))) {printf("Error: Prologue blocks dosn't have same size/alloc fields.\n");return 0;}if (GET_ALLOC(HDRP(heap_listp)) != 1 || GET_SIZE(HDRP(heap_listp)) != 8) {printf("Error: Prologue blocks dosn't have special size/alloc fields.\n");return 0;}//chekc epilogue blocksfor (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {}if (curbp != endp) {printf("Error: A block with size 0 isn't endp\n");printf("Its size is %d, address is %p and alloc is %d\n", GET_SIZE(HDRP(curbp)), curbp, GET_ALLOC(HDRP(curbp)));return 0;}if (GET_ALLOC(HDRP(endp)) != 1 || GET_SIZE(HDRP(endp)) != 0) {printf("Error: Epilogue blocks are not at specific locations.\n");return 0;}return 1;
}
*/
#### 调试信息
部分调试打印信息:(可以清晰地看出我们的设计方案)
// ./mdriver -V -f short1-bal.rep > out.txt=========================== mm_init ===========================
address = 0xf698f018
hsize = 8, fsize = 8
halloc = 1, falloc = 1address = 0xf698f020
hsize = 4096, fsize = 4096
halloc = 0, falloc = 0address = 0xf6990020
hsize = 0
halloc = 1
=========================== mm_init ====================================================== mm_malloc ===========================
address = 0xf698f018
hsize = 8, fsize = 8
halloc = 1, falloc = 1address = 0xf698f020
hsize = 2048, fsize = 2048
halloc = 1, falloc = 1address = 0xf698f820
hsize = 2048, fsize = 2048
halloc = 0, falloc = 0address = 0xf6990020
hsize = 0
halloc = 1
=========================== mm_malloc ===========================
跑分(隐式空闲链表+首次适配)
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.003636 15661 yes 99% 5848 0.003695 15832 yes 99% 6648 0.005280 12593 yes 100% 5380 0.003648 14754 yes 66% 14400 0.0000891612545 yes 92% 4800 0.004965 9676 yes 92% 4800 0.004495 10687 yes 55% 12000 0.086989 1388 yes 51% 24000 0.126514 1909 yes 27% 14401 0.028150 512
10 yes 30% 14401 0.000831 17330
Total 74% 112372 0.268292 419Perf index = 44 (util) + 28 (thru) = 72/100
优化方向
去除已分配块的脚部
- 已分配块无需脚部,只有空闲块需要脚部(因为只有前块为空闲块时才需要用到它的脚部用来合并)。
- 分析:个人认为对于大部分情况来说感觉对内存利用率提升不会太大。毕竟一个脚部才 4 B 4B 4B。
放置策略使用下一次适配(next fit)
注意!!! 使用下一次适配时会有一个问题,可能在下一次使用上次位置的指针时,此指针已不存在(具体表现在合并前块操作中,会将此块的指针合并掉)
修改代码find_fit 和 coalesce
static void* next_fitp; //下一次适配指向的指针
//在init函数中要对此指针进行赋值//find_fit版本1 使用do-while循环
static void *find_fit(size_t size) {char *endp, *lastp;next_fitp = NEXT_BLKP(next_fitp); //此次开始搜索的位置endp = (char *)mem_heap_hi() + 1;lastp = next_fitp;do {if (next_fitp == endp) {next_fitp = heap_listp;continue;}if (!GET_ALLOC(HDRP(next_fitp)) && (GET_SIZE(HDRP(next_fitp)) >= size)) return next_fitp;next_fitp = NEXT_BLKP(next_fitp);} while(next_fitp != lastp);return NULL;
}
//find_fit版本2 无脑用代码写两次大致相同的for循环
static void *find_fit(size_t size) {char *lastp;next_fitp = NEXT_BLKP(next_fitp); //此次搜索开始的位置lastp = next_fitp;for (;GET_SIZE(HDRP(next_fitp)) > 0; next_fitp = NEXT_BLKP(next_fitp)) {if (!GET_ALLOC(HDRP(next_fitp)) && (GET_SIZE(HDRP(next_fitp)) >= size)) {return next_fitp;}}next_fitp = NEXT_BLKP(heap_listp);for (;next_fitp != lastp; next_fitp = NEXT_BLKP(next_fitp)) {if (!GET_ALLOC(HDRP(next_fitp)) && (GET_SIZE(HDRP(next_fitp)) >= size)) {return next_fitp;}}return NULL;
}//只针对其中需要与前块合并时的代码进行修改
static void *coalesce(void *bp) { int pre_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));int post_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));size_t size = GET_SIZE(HDRP(bp));if (pre_alloc && post_alloc) {return bp;} else if (pre_alloc && !post_alloc) { //与后块合并size += GET_SIZE(HDRP(NEXT_BLKP(bp)));} else if (!pre_alloc && post_alloc) { //与前块合并size += GET_SIZE(HDRP(PREV_BLKP(bp)));if (bp == next_fitp) {next_fitp = PREV_BLKP(bp);}bp = PREV_BLKP(bp);} else { //前后块都合并size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));if (bp == next_fitp) {next_fitp = PREV_BLKP(bp);}bp = PREV_BLKP(bp);}PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0)); //FTRP()与GET_SIZE()有耦合,故此时所用的SIZE已经改变return bp;
}
跑分
Results for mm malloc:
trace valid util ops secs Kops0 yes 90% 5694 0.001078 52801 yes 91% 5848 0.000686 85262 yes 95% 6648 0.001864 35673 yes 96% 5380 0.001754 30674 yes 66% 14400 0.0000941535185 yes 91% 4800 0.002902 16546 yes 89% 4800 0.002691 17837 yes 55% 12000 0.009888 12148 yes 51% 24000 0.003605 66589 yes 27% 14401 0.028398 507
10 yes 45% 14401 0.000729 19754
Total 72% 112372 0.053688 2093Perf index = 43 (util) + 40 (thru) = 83/100
优化成果分析
分数对比:原44 (util) + 28 (thru) = 72/100
,现43 (util) + 40 (thru) = 83/100
。
首次适配:
- 优点:趋向于将大的空闲块保留在链表的后面
- 缺点:趋向于在靠近链表起始处留下小空闲块的“碎片”,增加了对较大块的搜索时间。
下一次适配:
- 优点:上一次在某个空闲块中发现匹配,下一次也有可能(倾向于)在这个剩余块中发现匹配。
- 缺点:研究表明,下一次适配的内存利用率要比首次适配低得多。
针对得到的两次分数,可以明显的看到下一次适配的吞吐率比首次适配高很多,这方面next fit
优势明显,但内存利用率要低。
三种放置策略:
研究表明,最佳适配比首次适配和下一次适配的内存利用率都要高一些,但在隐式空闲链表(简单空闲链表)中需要对堆进行彻底的搜索,而后面的分离式空闲链表组织会接近于最佳适配策略,而不需要进行彻底的堆搜索。
显式空闲链表
思想
在内存块的基本分配下,在空闲块中增加指向前后的指针。以提高搜索适配块的效率,缺点就是增加了最小块的限制,最小块必须把两个指针也放进去。以支持维持一个 freelist 表以便遍历。
- 空闲块组织:显式空闲链表,利用对齐块存放空闲链表的“头结点”。
- 放置:后进先出(LIFO),针对空闲链表使用头插法
- 分割:分割空闲块的前部使用。
- 合并:立即合并(immediate coalescing)
优缺点:
- 因为指针的开销,最小块限制增加为 24B , 潜在地提高了内部碎片的程度,但同样从块分配与堆块的总数呈线性关系降低至与空闲块数量相关的线性时间,改进还是相当大的。(可以很明显地从跑分中看出,内存利用率下降很少,但吞吐量却提升很大。)
- 利用双指针进行空闲链表的遍历增加了内部碎片的大小,但遍历时降为常数时间。
分割策略:
合并策略:
函数设计原则
与隐式空闲链表相比,只需在其基础上增加空闲块内的两指针,以及针对空闲链表进行的操作即可。
宏定义(对指针进行操作)
#define MINBLOCK (DSIZE + 2*WSIZE + 2*WSIZE) //头部、脚部、两指针、8字节数据
#define GETADDR(p) (*(unsigned int **)(p)) //读地址p处的一个指针
#define PUTADDR(p, addr) (*(unsigned int **)(p) = (unsigned int *)(addr)) //向地址p处写一个指针
#define PRED_POINT(bp) (bp) //指向祖先指针的指针
#define SUCC_POINT(bp) ((char*)(bp) + WSIZE) //指向后继指针的指针static void* head_free; //空闲链表的头结点,存放在堆区开关的对齐块中
函数
static void insert_freelist(void *bp);
使用头插法向空闲链表中插入空闲块static void remove_freelist(void *bp);
从空闲链表中移除空闲块,合并中使用(合并策略)static void place_freelist(void *bp);
对空闲链表中的空闲块进行前部分割(分割策略)static void *find_fit(size_t size);
针对链表进行搜索上的修改
代码以及跑分
代码mm.c
/** 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 */"ateam",/* First member's full name */"Harry Bovik",/* First member's email address */"bovik@cs.cmu.edu",/* Second member's full name (leave blank if none) */"",/* Second member's email address (leave blank if none) */""
};#define VERBOSE 0
#ifdef DEBUG
#define VERBOSE 1
#endif/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8/* rounds up to the nearest multiple of ALIGNMENT */ // 对 ALIGNMENT 倍数上取整的计算
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))/* 自定义的宏,有便于操作常量和指针运算 */
#define WSIZE 4 //字、脚部或头部的大小(字节)
#define DSIZE 8 //双字大小(字节)
#define CHUNKSIZE (1<<12) //扩展堆时的默认大小
#define MINBLOCK (DSIZE + 2*WSIZE + 2*WSIZE) //头部、脚部、两指针、8字节数据#define MAX(x, y) ((x) > (y) ? (x) : (y))#define PACK(size, alloc) ((size) | (alloc)) //将 size 和 allocated bit 合并为一个字#define GET(p) (*(unsigned int *)(p)) //读地址p处的一个字
#define PUT(p, val) (*(unsigned int *)(p) = (val)) //向地址p处写一个字
#define GETADDR(p) (*(unsigned int **)(p)) //读地址p处的一个指针
#define PUTADDR(p, addr) (*(unsigned int **)(p) = (unsigned int *)(addr)) //向地址p处写一个指针#define GET_SIZE(p) (GET(p) & ~0x07) //得到地址p处的 size
#define GET_ALLOC(p) (GET(p) & 0x1) //得到地址p处的 allocated bit
//block point --> bp指向有效载荷块指针
#define HDRP(bp) ((char*)(bp) - WSIZE) //获得头部的地址
#define FTRP(bp) ((char*)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) //获得脚部的地址, 与宏定义HDRP有耦合#define NEXT_BLKP(bp) ((char*)(bp) + GET_SIZE((char*)(bp) - WSIZE)) //计算后块的地址
#define PREV_BLKP(bp) ((char*)(bp) - GET_SIZE((char*)(bp) - DSIZE)) //计算前块的地址#define PRED_POINT(bp) (bp) //指向祖先指针的指针
#define SUCC_POINT(bp) ((char*)(bp) + WSIZE) //指向后继指针的指针static void* heap_listp; //指向序言块
static void* head_free; //空闲链表的头结点,存放在堆区开关的对齐块中
/* private functions */
static void *extend_heap(size_t size); //拓展堆块
static void *find_fit(size_t size); //寻找空闲块 first fit
static void place(void *bp, size_t size); //分割空闲块
static void *coalesce(void *bp); //合并空闲块
/* 链表操作 */
static void insert_freelist(void *bp);
static void remove_freelist(void *bp);
static void place_freelist(void *bp);
//check
static void mm_printblock(int verbose, const char* func);/* * mm_init - initialize the malloc package.*/
//设立序言块、结尾块,以及序言块前的对齐块(4B),总共需要4个4B的空间
int mm_init(void)
{if ((heap_listp = mem_sbrk(4*WSIZE)) == (void*)-1) return -1;PUTADDR(heap_listp, NULL); //堆起绐位置的对齐块,使bp对齐8字节PUT(heap_listp + 1*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp + 2*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp, PACK(0, 1)); //结尾块head_free = heap_listp; //利用对齐块存放空闲链表的头结点PUTADDR(head_free, NULL);heap_listp += (2*WSIZE); //小技巧:使heap_listp指向下一块, 即两个序主块中间if (extend_heap(CHUNKSIZE) == NULL) //拓展堆块return -1;mm_printblock(VERBOSE, __func__);return 0;
}//使用头插法,将空闲块插入空闲链表中
static void insert_freelist(void *bp) { //LIFO 后进先出,头插法if (GETADDR(head_free) == NULL) {PUTADDR(SUCC_POINT(bp), NULL);PUTADDR(PRED_POINT(bp), head_free);PUTADDR(head_free, bp);} else {void *tmp;tmp = GETADDR(head_free);PUTADDR(SUCC_POINT(bp), tmp);PUTADDR(PRED_POINT(bp), head_free);PUTADDR(head_free, bp);PUTADDR(PRED_POINT(tmp), bp);tmp = NULL;}
}//将 bp 所指的空闲块从空闲链表中移除(进行合并操作中会用到)
static void remove_freelist(void *bp) {void *pre_block, *post_block;pre_block = GETADDR(PRED_POINT(bp));post_block = GETADDR(SUCC_POINT(bp));//处理前序结点if (pre_block == head_free) {PUTADDR(head_free, post_block);} else {PUTADDR(SUCC_POINT(pre_block), post_block);}//处理后序结点if (post_block != NULL) {PUTADDR(PRED_POINT(post_block), pre_block);}
}//对空闲链表中的空闲块进行前部分割
static void place_freelist(void *bp) {void *pre_block, *post_block, *next_bp;//存储前后结点地址pre_block = GETADDR(PRED_POINT(bp));post_block = GETADDR(SUCC_POINT(bp));next_bp = NEXT_BLKP(bp);//处理新的bp,进行前后连接PUTADDR(PRED_POINT(next_bp), pre_block);PUTADDR(SUCC_POINT(next_bp), post_block);//处理前序结点 针对head_free是前序结点的特殊处理if (pre_block == head_free) {PUTADDR(head_free, next_bp);} else {PUTADDR(SUCC_POINT(pre_block), next_bp);}//处理后序结点if (post_block != NULL) {PUTADDR(PRED_POINT(post_block), next_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; //ajusted sizesize_t extendsize; //若无适配块则拓展堆的大小void *bp = NULL;if (size == 0) //无效的申请return NULL;asize = ALIGN(size + 2*WSIZE);if ((bp = find_fit(asize)) != NULL) {place(bp, asize);return bp;}//无足够空间的空闲块用来分配extendsize = MAX(asize, CHUNKSIZE);if ((bp = extend_heap(extendsize)) == NULL) {return NULL;} place(bp, asize);mm_printblock(VERBOSE, __func__);return bp;
}static void *extend_heap(size_t size) {size_t asize; void *bp;asize = ALIGN(size);if ((long)(bp = mem_sbrk(asize)) == -1)return NULL;PUT(HDRP(bp), PACK(asize, 0)); //HDRP(bp)指向原结尾块PUT(FTRP(bp), PACK(asize, 0)); PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); //新结尾块return coalesce(bp);
}//放置策略搜索 首次适配搜索
static void *find_fit(size_t size) { void *curbp;for (curbp = GETADDR(head_free); curbp != NULL; curbp = GETADDR(SUCC_POINT(curbp))) {if (GET_SIZE(HDRP(curbp)) >= size)return curbp;}return NULL; //未适配
} //分割空闲块
static void place(void *bp, size_t asize) { //注意最小块的限制(24B == MINBLOCK)size_t total_size = GET_SIZE(HDRP(bp));size_t remainder_size = total_size - asize;if (remainder_size >= MINBLOCK) {PUT(HDRP(bp), PACK(asize, 1));PUT(FTRP(bp), PACK(asize, 1));void *next_bp = NEXT_BLKP(bp);PUT(HDRP(next_bp), PACK(remainder_size, 0));PUT(FTRP(next_bp), PACK(remainder_size, 0));place_freelist(bp);} else { //没有已分配块或空闲块可以比最小块更小PUT(HDRP(bp), PACK(total_size, 1));PUT(FTRP(bp), PACK(total_size, 1));remove_freelist(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));coalesce(ptr);mm_printblock(VERBOSE, __func__);
}
/*
* coalesce - 合并内存块
*/
static void *coalesce(void *bp) {char *pre_block, *post_block;int pre_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));int post_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));size_t size = GET_SIZE(HDRP(bp));if (pre_alloc && post_alloc) {insert_freelist(bp);return bp;} else if (pre_alloc && !post_alloc) { //与后块合并size += GET_SIZE(HDRP(NEXT_BLKP(bp)));post_block = NEXT_BLKP(bp); //记录后块的指针remove_freelist(post_block);insert_freelist(bp);} else if (!pre_alloc && post_alloc) { //与前块合并size += GET_SIZE(HDRP(PREV_BLKP(bp)));bp = PREV_BLKP(bp);remove_freelist(bp);insert_freelist(bp);} else { //前后块都合并size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));pre_block = PREV_BLKP(bp);post_block = NEXT_BLKP(bp);bp = PREV_BLKP(bp);remove_freelist(pre_block);remove_freelist(post_block);insert_freelist(bp);}PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0));return bp;
}/** mm_realloc - Implemented simply in terms of mm_malloc and mm_free*/
void *mm_realloc(void *ptr, size_t size)
{size_t old_size, new_size, extendsize;void *old_ptr, *new_ptr;if (ptr == NULL) {return mm_malloc(size);}if (size == 0) {mm_free(ptr);return NULL;}new_size = ALIGN(size + 2*WSIZE);old_size = GET_SIZE(HDRP(ptr));old_ptr = ptr;if (old_size >= new_size) {if (old_size - new_size >= MINBLOCK) { //分割内存块place(old_ptr, new_size);mm_printblock(VERBOSE, __func__);return old_ptr;} else { //剩余块小于最小块大小,不分割mm_printblock(VERBOSE, __func__);return old_ptr;}} else { //释放原内存块,寻找新内存块if ((new_ptr = find_fit(new_size)) == NULL) { //无合适内存块extendsize = MAX(new_size, CHUNKSIZE);if ((new_ptr = extend_heap(extendsize)) == NULL) //拓展堆空间return NULL;}place(new_ptr, new_size);memcpy(new_ptr, old_ptr, old_size - 2*WSIZE);mm_free(old_ptr);mm_printblock(VERBOSE, __func__);return new_ptr;}
}static void mm_printblock(int verbose, const char* func) {if (!verbose) return;void *curbp;printf("\n=========================== $%s$ ===========================\n" ,func);printf("================ block ================\n");for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {printf("address = %p\n", curbp);printf("hsize = %d, fsize = %d\n", GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)));printf("halloc = %d, falloc = %d\n", GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));printf("\n");}//epilogue blocksprintf("address = %p\n", curbp);printf("hsize = %d\n", GET_SIZE(HDRP(curbp)));printf("halloc = %d\n", GET_ALLOC(HDRP(curbp)));printf("================ block ================\n");printf("\n");printf("=============== freelist ===============\n");for (curbp = GETADDR(head_free); curbp != NULL; curbp = GETADDR(SUCC_POINT(curbp))) {printf("address = %p, size = %d,%d, alloc = %d,%d\n",curbp, GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)), GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));}printf("address = %p\n", curbp);printf("=============== freelist ===============\n");printf("=========================== $%s$ ===========================\n" ,func);
}
调试信息
部分调试打印信息:(可以清晰地看出我们的设计方案)
# 进行初始化堆块后内存块以及空闲链表的情况=========================== $mm_init$ ===========================
================ block ================
address = 0xf69aa018
hsize = 8, fsize = 8
halloc = 1, falloc = 1address = 0xf69aa020
hsize = 4096, fsize = 4096
halloc = 0, falloc = 0address = 0xf69ab020
hsize = 0
halloc = 1
================ block =============================== freelist ===============
address = 0xf69aa020, size = 4096,4096, alloc = 0,0
address = (nil)
=============== freelist ===============
=========================== $mm_init$ ===========================
跑分(显式空闲链表 + LIFO)
# 显式空闲链表 + LIFO
Results for mm malloc:
trace valid util ops secs Kops0 yes 93% 5694 0.000093 609641 yes 94% 5848 0.000088 664552 yes 96% 6648 0.000150 442903 yes 97% 5380 0.000158 341154 yes 66% 14400 0.0001311097565 yes 89% 4800 0.000306 156866 yes 85% 4800 0.000360 133157 yes 55% 12000 0.001054 113858 yes 51% 24000 0.001897 126529 yes 26% 14401 0.032686 441
10 yes 30% 14401 0.000994 14486
Total 71% 112372 0.037918 2964Perf index = 43 (util) + 40 (thru) = 83/100
优化方向
按照地址顺序来维护链表代替LIFO
- 缺陷:插入结点时间由常数时间增加为线性时间
- 优势:按照地址排序的首次适配比 LIFO 排序的首次适配有更高的内存利用率,接近最佳适配的利用率。
修改代码 insert_freelist
static void insert_freelist(void *bp) { //按地址顺序维护链表(线性时间)void *pre_block, *post_block, *tmp;tmp = head_free;for (post_block = GETADDR(head_free); post_block != NULL; post_block = GETADDR(SUCC_POINT(post_block))) {if (post_block > bp) {pre_block = GETADDR(PRED_POINT(post_block));// bp 结点前后序块PUTADDR(PRED_POINT(bp), pre_block);PUTADDR(SUCC_POINT(bp), post_block);//前序块if (pre_block == head_free) {PUTADDR(head_free, bp);} else {PUTADDR(SUCC_POINT(pre_block), bp);}//后序块PUTADDR(PRED_POINT(post_block), bp);return;}tmp = post_block; //若只能插入链表未尾,则存储最后一个结点}// curbp == NULL//前序结点地址pre_block = tmp;//bp 结点前后序块PUTADDR(PRED_POINT(bp), pre_block);PUTADDR(SUCC_POINT(bp), NULL);//前序结点块if (pre_block == head_free) {PUTADDR(head_free, bp);} else {PUTADDR(SUCC_POINT(pre_block), bp);}
}//另外因调试的需要,部分修改了调试函数(关于freelist部分)
static void mm_printblock(int verbose, const char* func) {if (!verbose) return;void *curbp;printf("\n=========================== $%s$ ===========================\n" ,func);printf("================ block ================\n");for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {printf("address = %p\n", curbp);printf("hsize = %d, fsize = %d\n", GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)));printf("halloc = %d, falloc = %d\n", GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));printf("\n");}//epilogue blocksprintf("address = %p\n", curbp);printf("hsize = %d\n", GET_SIZE(HDRP(curbp)));printf("halloc = %d\n", GET_ALLOC(HDRP(curbp)));printf("================ block ================\n");printf("\n");printf("=============== freelist ===============\n");printf("head_address = %p, next_address = %p\n", head_free, GETADDR(head_free));for (curbp = GETADDR(head_free); curbp != NULL; curbp = GETADDR(SUCC_POINT(curbp))) {printf("address = %p, next_address = %p, size = %d\n",curbp, GETADDR(SUCC_POINT(curbp)), GET_SIZE(HDRP(curbp)));}printf("address = %p\n", curbp);printf("=============== freelist ===============\n");printf("=========================== $%s$ ===========================\n" ,func);
}
调试信息
# ./mdriver -V -f short1-bal.rep > out.txt
=========================== $mm_init$ ===========================
================ block ================
address = 0xf697d018
hsize = 8, fsize = 8
halloc = 1, falloc = 1address = 0xf697d020
hsize = 4096, fsize = 4096
halloc = 0, falloc = 0address = 0xf697e020
hsize = 0
halloc = 1
================ block =============================== freelist ===============
head_address = 0xf697d010, next_address = 0xf697d020
address = 0xf697d020, next_address = (nil), size = 4096
address = (nil)
=============== freelist ===============
=========================== $mm_init$ ===========================
跑分
# 显式空闲链表+按地址顺序维护链表
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.000137 414711 yes 99% 5848 0.000123 475062 yes 99% 6648 0.000168 395013 yes 99% 5380 0.000136 396464 yes 66% 14400 0.0001291113695 yes 92% 4800 0.001686 28466 yes 92% 4800 0.001677 28637 yes 55% 12000 0.012042 9968 yes 51% 24000 0.071326 3369 yes 27% 14401 0.029718 485
10 yes 30% 14401 0.000880 16361
Total 74% 112372 0.118024 952Perf index = 44 (util) + 40 (thru) = 84/100
优化成果分析
可以看出提升并不是很大(原为43 (util) + 40 (thru) = 83/100
,现为44 (util) + 40 (thru) = 84/100
)。
因为按照地址顺序来维护空闲链表更接近于最佳适配,故内存利用率有所提高。
而吞吐率方面因插入链表为线性时间,而原搜索适配空闲块为线性时间,方法修改后时间上只为原方法线性时间的常数倍,对大规模操作而言,几乎无变化。
分离的空闲链表
思想
正如我们在前面所看到的,一个使用单向空闲块链表的分配器需要与空闲块数量呈线性关系的时间来分配块,为了近似达到最佳适配以及更快寻找适配块,可以根据不同的_大小类_来维护多个空闲链表。本代码采用的每个大小类都是2的幂。
函数设计原则
static void *segList[25]; //左闭右开,根据MAX_HEAP(20*(1<<20)) 即最大不到1<<25
主要修改的是关于链表的操作,与单个链表类似。
同时,因为在realloc
的操作中,如果原内存块不够需求的话,之前的方案是直接寻找适配块,若寻找不到的话就新分配堆块进行操作,但我们没有考虑到的是若原块相邻的空闲块的话那就可以进行合并操作以存放新块,这样就提高了内存使用率,改进的realloc
迎运而生。
代码以及跑分
代码
/** 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 */"ateam",/* First member's full name */"Harry Bovik",/* First member's email address */"bovik@cs.cmu.edu",/* Second member's full name (leave blank if none) */"",/* Second member's email address (leave blank if none) */""
};#define VERBOSE 0
#ifdef DEBUG
#define VERBOSE 1
#endif/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8/* rounds up to the nearest multiple of ALIGNMENT */ // 对 ALIGNMENT 倍数上取整的计算
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))/* 自定义的宏,有便于操作常量和指针运算 */
#define WSIZE 4 //字、脚部或头部的大小(字节)
#define DSIZE 8 //双字大小(字节)
#define CHUNKSIZE (1<<12) //扩展堆时的默认大小
#define MINBLOCK (DSIZE + 2*WSIZE + 2*WSIZE) //头部、脚部、两指针、8字节数据#define MAX(x, y) ((x) > (y) ? (x) : (y))#define PACK(size, alloc) ((size) | (alloc)) //将 size 和 allocated bit 合并为一个字#define GET(p) (*(unsigned int *)(p)) //读地址p处的一个字
#define PUT(p, val) (*(unsigned int *)(p) = (val)) //向地址p处写一个字
#define GETADDR(p) (*(unsigned int **)(p)) //读地址p处的一个指针
#define PUTADDR(p, addr) (*(unsigned int **)(p) = (unsigned int *)(addr)) //向地址p处写一个指针#define GET_SIZE(p) (GET(p) & ~0x07) //得到地址p处的 size
#define GET_ALLOC(p) (GET(p) & 0x1) //得到地址p处的 allocated bit
//block point --> bp指向有效载荷块指针
#define HDRP(bp) ((char*)(bp) - WSIZE) //获得头部的地址
#define FTRP(bp) ((char*)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) //获得脚部的地址, 与宏定义HDRP有耦合#define NEXT_BLKP(bp) ((char*)(bp) + GET_SIZE((char*)(bp) - WSIZE)) //计算后块的地址
#define PREV_BLKP(bp) ((char*)(bp) - GET_SIZE((char*)(bp) - DSIZE)) //计算前块的地址#define PRED_POINT(bp) (bp) //指向祖先指针的指针
#define SUCC_POINT(bp) ((char*)(bp) + WSIZE) //指向后继指针的指针static void *heap_listp; //指向序言块
static void *segList[25]; //左闭右开,根据MAX_HEAP(20*(1<<20)) 即最大不到1<<25
/* private functions */
static void *extend_heap(size_t size); //拓展堆块
static void *find_fit(size_t size); //寻找空闲块 first fit
static void place(void *bp, size_t size); //分割空闲块
static void *coalesce(void *bp); //合并空闲块
static void *mm_realloc_coalesce(void *old_ptr, size_t new_size); //针对realloc优化的合并函数
/* 链表操作 */
static void insert_freelist(void *bp);
static void remove_freelist(void *bp);
static int isSegList(void *bp); //判断是否为segList
//check
static void mm_printblock(int verbose, const char* func);/* * mm_init - initialize the malloc package.*/
//设立序言块、结尾块,以及序言块前的对齐块(4B),总共需要4个4B的空间
int mm_init(void)
{for (int index = 0; index < 25; index++) {segList[index] = NULL;}if ((heap_listp = mem_sbrk(4*WSIZE)) == (void*)-1) return -1;PUTADDR(heap_listp, NULL); //堆起绐位置的对齐块,使bp对齐8字节PUT(heap_listp + 1*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp + 2*WSIZE, PACK(8, 1)); //序言块PUT(heap_listp, PACK(0, 1)); //结尾块heap_listp += (2*WSIZE); //小技巧:使heap_listp指向下一块, 即两个序主块中间if (extend_heap(CHUNKSIZE) == NULL) //拓展堆块return -1;mm_printblock(VERBOSE, __func__);return 0;
}static int isSegList(void *bp) {if (bp >= segList && bp <= (segList+23))return 1;return 0;
}//寻找适合大小的空闲链表并使用头插法插入
static void insert_freelist(void *bp) {size_t size;int index;size = GET_SIZE(HDRP(bp));for (index = 4; index < 25; index++) { //最小块为16B,即下标从4起有效if ((1 << index) <= size && (1 << (index+1)) > size)break;}if (segList[index] == NULL) {PUTADDR(SUCC_POINT(bp), NULL);PUTADDR(PRED_POINT(bp), &segList[index]); //为了判断该结点的前序结点是segList,将些赋值为NULLsegList[index] = bp;} else {void *tmp;tmp = segList[index];PUTADDR(SUCC_POINT(bp), tmp);PUTADDR(PRED_POINT(bp), &segList[index]);segList[index] = bp;PUTADDR(PRED_POINT(tmp), bp);tmp = NULL;}
}//将 bp 所指的空闲块从空闲链表中移除(进行合并、放置操作中会用到)
static void remove_freelist(void *bp) {void *pre_block, *post_block;pre_block = GETADDR(PRED_POINT(bp));post_block = GETADDR(SUCC_POINT(bp));//处理前序结点if (isSegList(pre_block)) { //前序是头结点PUTADDR(pre_block, post_block);} else {PUTADDR(SUCC_POINT(pre_block), post_block);}//处理后序结点if (post_block != NULL) {PUTADDR(PRED_POINT(post_block), pre_block);}
}/* * 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; //ajusted sizesize_t extendsize; //若无适配块则拓展堆的大小void *bp = NULL;if (size == 0) //无效的申请return NULL;asize = ALIGN(size + 2*WSIZE);if ((bp = find_fit(asize)) != NULL) {place(bp, asize);mm_printblock(VERBOSE, __func__);return bp;}//无足够空间的空闲块用来分配extendsize = MAX(asize, CHUNKSIZE);if ((bp = extend_heap(extendsize)) == NULL) {return NULL;} place(bp, asize);mm_printblock(VERBOSE, __func__);return bp;
}static void *extend_heap(size_t size) {size_t asize; void *bp;asize = ALIGN(size);if ((long)(bp = mem_sbrk(asize)) == -1)return NULL;PUT(HDRP(bp), PACK(asize, 0)); //HDRP(bp)指向原结尾块PUT(FTRP(bp), PACK(asize, 0)); PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); //新结尾块return coalesce(bp);
}//放置策略搜索 首次适配搜索+分离适配
static void *find_fit(size_t size) { for (int index = 4; index < 25; index++) {if (size < (1 << (index+1))) {unsigned int *curbp;for (curbp = segList[index]; curbp != NULL; curbp = GETADDR(SUCC_POINT(curbp))) {if (size <= GET_SIZE(HDRP(curbp))) {return curbp;}}}}return NULL; //未适配
} //分割空闲块
static void place(void *bp, size_t asize) { //注意最小块的限制(24B == MINBLOCK)size_t total_size = GET_SIZE(HDRP(bp));size_t remainder_size = total_size - asize;if (remainder_size >= MINBLOCK) {PUT(HDRP(bp), PACK(asize, 1));PUT(FTRP(bp), PACK(asize, 1));remove_freelist(bp);void *next_bp = NEXT_BLKP(bp);PUT(HDRP(next_bp), PACK(remainder_size, 0));PUT(FTRP(next_bp), PACK(remainder_size, 0));insert_freelist(next_bp);} else { //没有已分配块或空闲块可以比最小块更小PUT(HDRP(bp), PACK(total_size, 1));PUT(FTRP(bp), PACK(total_size, 1));remove_freelist(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));coalesce(ptr);mm_printblock(VERBOSE, __func__);
}
/*
* coalesce - 合并内存块
*/
static void *coalesce(void *bp) {char *pre_block, *post_block;int pre_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));int post_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));size_t size = GET_SIZE(HDRP(bp));if (pre_alloc && post_alloc) {insert_freelist(bp);return bp;} else if (!pre_alloc && post_alloc) { //与前块合并size += GET_SIZE(HDRP(PREV_BLKP(bp)));bp = PREV_BLKP(bp);remove_freelist(bp);} else if (pre_alloc && !post_alloc) { //与后块合并size += GET_SIZE(HDRP(NEXT_BLKP(bp)));post_block = NEXT_BLKP(bp); //记录后块的指针remove_freelist(post_block);} else { //前后块都合并size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));pre_block = PREV_BLKP(bp);post_block = NEXT_BLKP(bp);bp = PREV_BLKP(bp);remove_freelist(pre_block);remove_freelist(post_block);}PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0));insert_freelist(bp);return bp;
}/** mm_realloc - Implemented simply in terms of mm_malloc and mm_free*/
void *mm_realloc(void *ptr, size_t size)
{size_t old_size, new_size, extendsize;void *old_ptr, *new_ptr;if (ptr == NULL) {return mm_malloc(size);}if (size == 0) {mm_free(ptr);return NULL;}new_size = ALIGN(size + 2*WSIZE);old_size = GET_SIZE(HDRP(ptr));old_ptr = ptr;if (old_size >= new_size) {if (old_size - new_size >= MINBLOCK) { //分割内存块place(old_ptr, new_size);mm_printblock(VERBOSE, __func__);return old_ptr;} else { //剩余块小于最小块大小,不分割mm_printblock(VERBOSE, __func__);return old_ptr;}} else { //寻找合并内存块或新内存块if ((new_ptr = mm_realloc_coalesce(old_ptr, new_size)) != NULL) { //合并相邻内存块并数据迁移后返回mm_printblock(VERBOSE, __func__);return new_ptr;}if ((new_ptr = find_fit(new_size)) == NULL) { //无合适内存块extendsize = MAX(new_size, CHUNKSIZE);if ((new_ptr = extend_heap(extendsize)) == NULL) //拓展堆空间return NULL;}//针对非相邻内存块进行数据迁移place(new_ptr, new_size);memcpy(new_ptr, old_ptr, old_size - 2*WSIZE);mm_free(old_ptr);mm_printblock(VERBOSE, __func__);return new_ptr;}
}//针对realloc的前后合并
static void *mm_realloc_coalesce(void *old_ptr, size_t new_size) {void *pre_block, *post_block, *new_ptr;int pre_alloc, post_alloc;size_t pre_size, post_size, old_size, total_size;pre_block = PREV_BLKP(old_ptr);post_block = NEXT_BLKP(old_ptr);pre_alloc = GET_ALLOC(HDRP(pre_block));post_alloc = GET_ALLOC(HDRP(post_block));pre_size = GET_SIZE(HDRP(pre_block));post_size = GET_SIZE(HDRP(post_block));old_size = GET_SIZE(HDRP(old_ptr));if (!pre_alloc && ((total_size = old_size + pre_size) >= new_size)) { //与前块合并分配new_ptr = pre_block;remove_freelist(pre_block);} else if (!post_alloc && ((total_size = old_size + post_size) >= new_size)){ //与后块合并分配new_ptr = old_ptr;remove_freelist(post_block);} else if (!pre_alloc && !post_alloc && ((total_size = old_size + pre_size + post_size) >= new_size)){ //与前后块合并分配new_ptr = pre_block;remove_freelist(pre_block);remove_freelist(post_block);} else { //无合并分配的可能return NULL;}memcpy(new_ptr, old_ptr, old_size - 2*WSIZE);if (total_size - new_size >= MINBLOCK) {PUT(HDRP(new_ptr), PACK(new_size, 1));PUT(FTRP(new_ptr), PACK(new_size, 1));void *next_bp = NEXT_BLKP(new_ptr);PUT(HDRP(next_bp), PACK(total_size - new_size, 0));PUT(FTRP(next_bp), PACK(total_size - new_size, 0));coalesce(next_bp);} else {PUT(HDRP(new_ptr), PACK(total_size, 1));PUT(FTRP(new_ptr), PACK(total_size, 1));}return new_ptr;
}static void mm_printblock(int verbose, const char* func) {if (!verbose) return;void *curbp;printf("\n=========================== $%s$ ===========================\n" ,func);printf("================ block ================\n");for (curbp = heap_listp; GET_SIZE(HDRP(curbp)) > 0; curbp = NEXT_BLKP(curbp)) {printf("address = %p\n", curbp);printf("size = %d, %d, alloc = %d, %d\n", GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)), GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));printf("\n");}//epilogue blocksprintf("address = %p\n", curbp);printf("size = %d, alloc = %d\n", GET_SIZE(HDRP(curbp)), GET_ALLOC(HDRP(curbp)));printf("================ block ================\n");printf("\n");printf("=============== freelist ===============\n");for (int index = 4; index < 25; index++) {if (segList[index] == NULL) continue;printf("segList[%d]: [%d,%d)\n", index, (1 << index), (1 << (index+1)));for (curbp = segList[index]; curbp != NULL; curbp = GETADDR(SUCC_POINT(curbp))) {printf("address = %p, size = %d,%d, alloc = %d,%d\n",curbp, GET_SIZE(HDRP(curbp)), GET_SIZE(FTRP(curbp)), GET_ALLOC(HDRP(curbp)), GET_ALLOC(FTRP(curbp)));}printf("address = %p\n", curbp);}printf("=============== freelist ===============\n");printf("=========================== $%s$ ===========================\n" ,func);
}
调试信息
=========================== $mm_malloc$ ===========================
================ block ================
address = 0xf69df018
size = 8, 8, alloc = 1, 1address = 0xf69df020
size = 2104, 2104, alloc = 0, 0address = 0xf69df858
size = 4080, 4080, alloc = 1, 1address = 0xf69e0848
size = 4080, 4080, alloc = 1, 1address = 0xf69e1838
size = 2024, 2024, alloc = 0, 0address = 0xf69e2020
size = 0, alloc = 1
================ block =============================== freelist ===============
segList[10]: [1024,2048)
address = 0xf69e1838, size = 2024,2024, alloc = 0,0
address = (nil)
segList[11]: [2048,4096)
address = 0xf69df020, size = 2104,2104, alloc = 0,0
address = (nil)
=============== freelist ===============
=========================== $mm_malloc$ ===========================
跑分
Results for mm malloc:
trace valid util ops secs Kops0 yes 98% 5694 0.000287 198331 yes 97% 5848 0.000311 187802 yes 99% 6648 0.000322 206523 yes 99% 5380 0.000278 193394 yes 66% 14400 0.000528 272835 yes 93% 4800 0.000459 104626 yes 90% 4800 0.000377 127197 yes 55% 12000 0.000447 268648 yes 51% 24000 0.001032 232549 yes 45% 14401 0.023348 617
10 yes 45% 14401 0.001142 12609
Total 76% 112372 0.028531 3939Perf index = 46 (util) + 40 (thru) = 86/100
优化方向
最佳适配(与分离链表的组合)
//寻找适合大小的空闲链表并按照从小到插入,这样利用分离适配不需要搜索所有的堆就可达到最佳适配的效果
static void insert_freelist(void *bp) {size_t size;int index;size = GET_SIZE(HDRP(bp));for (index = 4; index < 25; index++) { //最小块为16B,即下标从4起有效if ((1 << index) <= size && (1 << (index+1)) > size)break;}void *pre_block, *post_block, *tmp;tmp = segList + index;for (post_block = segList[index]; post_block != NULL; post_block = GETADDR(SUCC_POINT(post_block))) {if (GET_SIZE(HDRP(post_block)) >= size) {pre_block = GETADDR(PRED_POINT(post_block));// bp 结点前后序块PUTADDR(PRED_POINT(bp), pre_block);PUTADDR(SUCC_POINT(bp), post_block);//前序块if (isSegList(pre_block)) {PUTADDR(pre_block, bp);} else {PUTADDR(SUCC_POINT(pre_block), bp);}//后序块PUTADDR(PRED_POINT(post_block), bp);return;}tmp = post_block; //若只能插入链表未尾,则存储最后一个结点}//前序结点地址pre_block = tmp;//bp 结点前后序块PUTADDR(PRED_POINT(bp), pre_block);PUTADDR(SUCC_POINT(bp), NULL);//前序结点块if (isSegList(pre_block)) {PUTADDR(pre_block, bp);} else {PUTADDR(SUCC_POINT(pre_block), bp);}
}
跑分
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.000171 332401 yes 99% 5848 0.000147 396742 yes 99% 6648 0.000192 346613 yes 99% 5380 0.000138 389864 yes 66% 14400 0.000226 637175 yes 96% 4800 0.000343 139786 yes 95% 4800 0.000341 140687 yes 55% 12000 0.000345 347328 yes 51% 24000 0.000608 394879 yes 40% 14401 0.022937 628
10 yes 45% 14401 0.000986 14611
Total 77% 112372 0.026435 4251Perf index = 46 (util) + 40 (thru) = 86/100
优化成果分析
与分离链表+首次适配的得分一样啊,内存利用率竟然没有提升。。。。
总结
隐式空闲链表+首次适配+原始realloc
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.003636 15661 yes 99% 5848 0.003695 15832 yes 99% 6648 0.005280 12593 yes 100% 5380 0.003648 14754 yes 66% 14400 0.0000891612545 yes 92% 4800 0.004965 9676 yes 92% 4800 0.004495 10687 yes 55% 12000 0.086989 1388 yes 51% 24000 0.126514 1909 yes 27% 14401 0.028150 512
10 yes 30% 14401 0.000831 17330
Total 74% 112372 0.268292 419Perf index = 44 (util) + 28 (thru) = 72/100
隐式空闲链表+下一次适配+原始realloc
Results for mm malloc:
trace valid util ops secs Kops0 yes 90% 5694 0.001078 52801 yes 91% 5848 0.000686 85262 yes 95% 6648 0.001864 35673 yes 96% 5380 0.001754 30674 yes 66% 14400 0.0000941535185 yes 91% 4800 0.002902 16546 yes 89% 4800 0.002691 17837 yes 55% 12000 0.009888 12148 yes 51% 24000 0.003605 66589 yes 27% 14401 0.028398 507
10 yes 45% 14401 0.000729 19754
Total 72% 112372 0.053688 2093Perf index = 43 (util) + 40 (thru) = 83/100
显式空闲链表+LIFO(首次适配)+原始realloc
# 显式空闲链表 + LIFO
Results for mm malloc:
trace valid util ops secs Kops0 yes 93% 5694 0.000093 609641 yes 94% 5848 0.000088 664552 yes 96% 6648 0.000150 442903 yes 97% 5380 0.000158 341154 yes 66% 14400 0.0001311097565 yes 89% 4800 0.000306 156866 yes 85% 4800 0.000360 133157 yes 55% 12000 0.001054 113858 yes 51% 24000 0.001897 126529 yes 26% 14401 0.032686 441
10 yes 30% 14401 0.000994 14486
Total 71% 112372 0.037918 2964Perf index = 43 (util) + 40 (thru) = 83/100
显式空闲链表+按地址排序(首次适配)+原始realloc
# 显式空闲链表+按地址顺序维护链表
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.000137 414711 yes 99% 5848 0.000123 475062 yes 99% 6648 0.000168 395013 yes 99% 5380 0.000136 396464 yes 66% 14400 0.0001291113695 yes 92% 4800 0.001686 28466 yes 92% 4800 0.001677 28637 yes 55% 12000 0.012042 9968 yes 51% 24000 0.071326 3369 yes 27% 14401 0.029718 485
10 yes 30% 14401 0.000880 16361
Total 74% 112372 0.118024 952Perf index = 44 (util) + 40 (thru) = 84/100
分离适配+首次适配+改进 realloc
Results for mm malloc:
trace valid util ops secs Kops0 yes 98% 5694 0.000287 198331 yes 97% 5848 0.000311 187802 yes 99% 6648 0.000322 206523 yes 99% 5380 0.000278 193394 yes 66% 14400 0.000528 272835 yes 93% 4800 0.000459 104626 yes 90% 4800 0.000377 127197 yes 55% 12000 0.000447 268648 yes 51% 24000 0.001032 232549 yes 45% 14401 0.023348 617
10 yes 45% 14401 0.001142 12609
Total 76% 112372 0.028531 3939Perf index = 46 (util) + 40 (thru) = 86/100
分离适配+最佳适配+改进 realloc
Results for mm malloc:
trace valid util ops secs Kops0 yes 99% 5694 0.000171 332401 yes 99% 5848 0.000147 396742 yes 99% 6648 0.000192 346613 yes 99% 5380 0.000138 389864 yes 66% 14400 0.000226 637175 yes 96% 4800 0.000343 139786 yes 95% 4800 0.000341 140687 yes 55% 12000 0.000345 347328 yes 51% 24000 0.000608 394879 yes 40% 14401 0.022937 628
10 yes 45% 14401 0.000986 14611
Total 77% 112372 0.026435 4251Perf index = 46 (util) + 40 (thru) = 86/100
会发现吞吐率很容易拿到满分,而内存利用率才是更为重要的选项.
但这个方法都用上了,搞不清楚为什么内存利用率没有提升…
CSAPP(CMU 15-213):Lab6 Malloclab详解相关推荐
- 一文弄懂元学习 (Meta Learing)(附代码实战)《繁凡的深度学习笔记》第 15 章 元学习详解 (上)万字中文综述
<繁凡的深度学习笔记>第 15 章 元学习详解 (上)万字中文综述(DL笔记整理系列) 3043331995@qq.com https://fanfansann.blog.csdn.net ...
- mysql8.0.20 64位安装教程_windows 64位下MySQL 8.0.15安装教程图文详解
先去官网下载点击的MySQL的下载 下载完成后解压 解压完是这个样子 配置系统环境变量 复制解压后的mysql到C盘或者其他磁盘下 我们去系统的环境变量的path里添加一个mysql的配置 指向my ...
- mysql 8.064位安装_windows64位下MySQL8.0.15安装教程图文详解
先去官网下载点击的MySQL的下载 下载完成后解压 解压完是这个样子 配置系统环境变量 复制解压后的mysql到C盘或者其他磁盘下 我们去系统的环境变量的path里添加一个mysql的配置 指向my ...
- 走穿java23种设计模式-15责任链模式详解
走穿java23种设计模式-15责任链模式详解 责任链模式是一种常见的行为模式. 一.责任链模式的现实场景 习伟过生日邀请了很多朋友到KTV一起庆祝,为了增加欢乐的气氛,习伟建议大家一起玩击鼓传花的游 ...
- OpenCV实战(15)——轮廓检测详解
OpenCV实战(15)--轮廓检测详解 0. 前言 1. 提取区域轮廓 1.1 轮廓提取 1.2 复杂轮廓分析 2. 计算区域形状描述符 2.1 四边形检测 3. 完整代码 小结 系列链接 0. 前 ...
- 15.linux-LCD层次分析(详解)
如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口. 什么是frambuffer设备? frambu ...
- 【纯新手教程】mysql8.0.15下载安装教程详解
背景:作为一个热爱技术但不懂代码的产品写的水水教程O(∩_∩)O哈哈~ 1.环境 系统:windows 64位 mysql版本:mysql 8.0.15 2.下载篇 首先是下载数据库安装文件,进入my ...
- 【Tools】VMware Workstation 15.5 Pro安装详解
00. 目录 文章目录 00. 目录 01. VMware Workstation 15.5简介 02. VMware Workstation 15.5功能 03. VMware Workstatio ...
- Python编程-从入门到实践第15章课后习题详解
第15章 使用Plotly模拟掷骰子--课后习题答案 练习15-6 #掷两个D8 from plotly.graph_objs import Bar,Layout from plotly import ...
最新文章
- [UWP小白日记-10]程序启动屏(ios解锁既视感)
- [Z]从铁道部12306.cn网站漫谈电子商务网站的“海量事务高速处理”系统
- AjaxPro怎么用
- 前端 CSS Framework --- NEC (网易)
- tabula-java_Java FileSystems.getDefault方法代码示例
- python接口自动化(二)--什么是接口测试、为什么要做接口测试(详解)
- qfiledialog的取消_QFileDialog类(老九学堂C++会员友情翻译,不喜勿喷)
- dorado 刷新_dorado BDF常见问题
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_09-freemarker基础-内建函数
- Android Monkey Test
- 多变量微积分笔记5——梯度与方向导数
- 计算机网络辩论赛主持词,辩论赛主持词(精选)
- PDF怎么转换成长图
- 如何在直播中解决黑屏、花屏、闪屏问题
- Golang 的 mgo 连接池
- CVE的提交与Github写入技巧
- 洧 誰 理 夲 亾 と 杺
- 经验分享:Python3离线安装及库更新
- NRF52832学习笔记(19)——TWI(I2C)接口使用
- Hevc Cabac(一)