Carnegie Mellon University의 Malloc Lab 과제
out of memory
에러가 발생한다.out of memory
에러를 없애는 과제이다. int mm_init(void); // 초기화
void *mm_malloc(size_t size); // 할당
void mm_free(void *ptr); // 해제
void *mm_realloc(void *ptr, size_t size); // 재할당
int mm_init(void);
int mm_init(void);
size
크기의 블록에 할당되어야 한다.void mm_free(void *ptr);
void *mm_realloc(void *ptr, size_t size);
size
크기보다 같거나 큰 영역으로 할당된 영역에 대한 포인터를 반환한다.ptr
이 NULL인 경우size
가 0인 경우ptr
이 NULL이 아닌 경우ptr
은 이전에 mm_malloc 또는 mm_realloc 호출에서 반환된 포인터여야 한다.ptr
이 가리키는 이전 블록의 크기를 size
크기로 변경하고 새 블록의 주소를 반환한다.Github에서 'Implicit List' 전체 코드 보기
🧮 Score: 54점 (이용도 44 + 처리량 9)
1) 블록 할당
사용 여부
와 블록의 사이즈
를 각 블록 내부의 header 및 footer에 저장한다.크기가 맞는 첫번째 가용 블록
을 선택한다. (First fit)나머지는 분할
하여 또다른 가용 블록으로 남겨둔다.2) 블록 반환
/* 기본 상수 */
#define WSIZE 4 // word size
#define DSIZE 8 // double word size
#define CHUNKSIZE (1 << 12) // 힙 확장을 위한 기본 크기 (= 초기 빈 블록의 크기)
/* 매크로 */
#define MAX(x, y) (x > y ? x : y)
// size와 할당 비트를 결합, header와 footer에 저장할 값
#define PACK(size, alloc) (size | alloc)
// p가 참조하는 워드 반환 (포인터라서 직접 역참조 불가능 -> 타입 캐스팅)
#define GET(p) (*(unsigned int *)(p))
// p에 val 저장
#define PUT(p, val) (*(unsigned int *)(p) = (val))
// 사이즈 (~0x7: ...11111000, '&' 연산으로 뒤에 세자리 없어짐)
#define GET_SIZE(p) (GET(p) & ~0x7)
// 할당 상태
#define GET_ALLOC(p) (GET(p) & 0x1)
// Header 포인터
#define HDRP(bp) ((char *)(bp)-WSIZE)
// Footer 포인터 (🚨Header의 정보를 참조해서 가져오기 때문에, Header의 정보를 변경했다면 변경된 위치의 Footer가 반환됨에 유의)
#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)))
int mm_init(void)
{
// 초기 힙 생성
char *heap_listp;
if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1) // 4워드 크기의 힙 생성, heap_listp에 힙의 시작 주소값 할당
return -1;
PUT(heap_listp, 0); // 정렬 패딩
PUT(heap_listp + (1 * WSIZE), PACK(DSIZE, 1)); // 프롤로그 Header
PUT(heap_listp + (2 * WSIZE), PACK(DSIZE, 1)); // 프롤로그 Footer
PUT(heap_listp + (3 * WSIZE), PACK(0, 1)); // 에필로그 Header: 프로그램이 할당한 마지막 블록의 뒤에 위치하며, 블록이 할당되지 않은 상태를 나타냄
// 힙을 CHUNKSIZE bytes로 확장
if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
return -1;
return 0;
}
size
를 조정한다.find_fit
함수 호출)place
함수 호출)extend_heap
함수 호출)place
함수 호출)void *mm_malloc(size_t size)
{
size_t asize; // 조정된 블록 사이즈
size_t extendsize; // 확장할 사이즈
char *bp;
// 잘못된 요청 분기
if (size == 0)
return NULL;
/* 사이즈 조정 */
if (size <= DSIZE) // 8바이트 이하이면
asize = 2 * DSIZE; // 최소 블록 크기 16바이트 할당 (헤더 4 + 푸터 4 + 저장공간 8)
else
asize = DSIZE * ((size + DSIZE + DSIZE - 1) / DSIZE); // 8배수로 올림 처리
/* 가용 블록 검색 */
if ((bp = find_fit(asize)) != NULL)
{
place(bp, asize); // 할당
return bp; // 새로 할당된 블록의 포인터 리턴
}
/* 적합한 블록이 없을 경우 힙확장 */
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize / WSIZE)) == NULL)
return NULL;
place(bp, asize);
return bp;
}
가용 블록 검색
asize
를 담을 수 있는 블록을 찾을 때까지 탐색을 이어간다. (First-fit)static void *find_fit(size_t asize)
{
void *bp = mem_heap_lo() + 2 * WSIZE; // 첫번째 블록(주소: 힙의 첫 부분 + 8bytes)부터 탐색 시작
while (GET_SIZE(HDRP(bp)) > 0)
{
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp)))) // 가용 상태이고, 사이즈가 적합하면
return bp; // 해당 블록 포인터 리턴
bp = NEXT_BLKP(bp); // 조건에 맞지 않으면 다음 블록으로 이동해서 탐색을 이어감
}
return NULL;
}
할당
나머지는 분할
하여 또다른 가용 블록으로 남겨둔다.static void place(void *bp, size_t asize)
{
size_t csize = GET_SIZE(HDRP(bp)); // 현재 블록의 크기
if ((csize - asize) >= (2 * DSIZE)) // 차이가 최소 블록 크기 16보다 같거나 크면 분할
{
PUT(HDRP(bp), PACK(asize, 1)); // 현재 블록에는 필요한 만큼만 할당
PUT(FTRP(bp), PACK(asize, 1));
PUT(HDRP(NEXT_BLKP(bp)), PACK((csize - asize), 0)); // 남은 크기를 다음 블록에 할당(가용 블록)
PUT(FTRP(NEXT_BLKP(bp)), PACK((csize - asize), 0));
}
else
{
PUT(HDRP(bp), PACK(csize, 1)); // 해당 블록 전부 사용
PUT(FTRP(bp), PACK(csize, 1));
}
}
힙 확장
mem_sbrk
함수 요청)static void *extend_heap(size_t words)
{
char *bp;
// 더블 워드 정렬 유지
// size: 확장할 크기
size_t size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE; // 2워드의 가장 가까운 배수로 반올림 (홀수면 1 더해서 곱함)
if ((long)(bp = mem_sbrk(size)) == -1) // 힙 확장
return NULL;
PUT(HDRP(bp), PACK(size, 0)); // 새 빈 블록 헤더 초기화
PUT(FTRP(bp), PACK(size, 0)); // 새 빈 블록 푸터 초기화
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); // 에필로그 블록 헤더 초기화
return coalesce(bp); // 병합 후 coalesce 함수에서 리턴된 블록 포인터 반환
}
PACK
매크로 호출)coalesce
함수 호출)void mm_free(void *bp)
{
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
coalesce(bp);
}
병합
GET_ALLOC
매크로 호출)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) // 모두 할당된 경우
return bp;
else if (prev_alloc && !next_alloc) // 다음 블록만 빈 경우
{
size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
PUT(HDRP(bp), PACK(size, 0)); // 현재 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 다음 블록 푸터 재설정 (위에서 헤더를 재설정했으므로, FTRP(bp)는 합쳐질 다음 블록의 푸터가 됨)
}
else if (!prev_alloc && next_alloc) // 이전 블록만 빈 경우
{
size += GET_SIZE(HDRP(PREV_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 현재 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
else // 이전 블록과 다음 블록 모두 빈 경우
{
size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); // 다음 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
return bp; // 병합된 블록의 포인터 반환
}
예외 처리
ptr
이 NULL인 경우에는 할당만 수행한다. (mm_malloc
함수 호출)size
가 0인 경우에는 메모리 반환만 수행한다. (mm_free
함수 호출)새 블록에 할당
size
만큼 할당할 수 있는 블록을 찾아 할당한다. (mm_malloc
함수 호출)데이터 복사
ptr
이 가리키고 있던 블록이 가지고 있는 payload 크기를 구하고,size
보다 기존의 크기가 크다면 size로 크기를 조정한다.size
만큼 새로 할당한 블록으로 데이터를 복사한다. (memcpy
함수 호출)기존 블록 반환
mm_free
함수 호출)// 기존에 할당된 메모리 블록의 크기 변경
// `기존 메모리 블록의 포인터`, `새로운 크기`
void *mm_realloc(void *ptr, size_t size)
{
/* 예외 처리 */
if (ptr == NULL) // 포인터가 NULL인 경우 할당만 수행
return mm_malloc(size);
if (size <= 0) // size가 0인 경우 메모리 반환만 수행
{
mm_free(ptr);
return 0;
}
/* 새 블록에 할당 */
void *newptr = mm_malloc(size); // 새로 할당한 블록의 포인터
if (newptr == NULL)
return NULL; // 할당 실패
/* 데이터 복사 */
size_t copySize = GET_SIZE(HDRP(ptr)) - DSIZE; // payload만큼 복사
if (size < copySize) // 기존 사이즈가 새 크기보다 더 크면
copySize = size; // size로 크기 변경 (기존 메모리 블록보다 작은 크기에 할당하면, 일부 데이터만 복사)
memcpy(newptr, ptr, copySize); // 새 블록으로 데이터 복사
/* 기존 블록 반환 */
mm_free(ptr);
return newptr;
}
Github에서 'LIFO 순서 정책' 전체 코드 보기
🧮 Explicit List 방식을 사용하면 블록 할당 시 전체 블록이 아닌 가용 블록만 탐색하게 되어서 Implicit List 방식에 비해 처리량이 3~5배 좋아졌다. 👍
(가용 리스트 정렬 방식에 따라 처리량 점수가 다르게 책정되었다.)1) ’LIFO 순서’ 정책
Score: 85점 (이용도 45 + 처리량 40)2) ‘주소 순서’ 정책
Score: 74점 (이용도 46 + 처리량 28)
3) ‘사이즈 순서’ 정책
Score: 74점 (이용도 45 + 처리량 29)
1) 명시적 가용 리스트 활용
predeccessor
와 successor
포인터를 포함해 각 블록 간 연결 리스트 형태로 만든다.2) 블록 할당
크기가 맞는 첫번째 가용 블록
을 선택한다. (First fit)나머지는 분할
하여 또다른 가용 블록으로 남겨둔다.3) 블록 반환
‘LIFO 순서’ 정책
- 만들어진 가용 블록을 가용 리스트의 첫부분에 삽입한다.
‘주소 순서’ 정책
- 만들어진 가용 블록을 가용 리스트에 삽입할 때 주소 오름차순으로 정렬이 되도록 위치를 탐색한 후 삽입한다.
#define GET_SUCC(bp) (*(void **)((char *)(bp) + WSIZE)) // 다음 가용 블록의 주소
#define GET_PRED(bp) (*(void **)(bp)) // 이전 가용 블록의 주소
predeccessor
와 successor
를 NULL로 초기화한다.free_listp
가 첫번째 가용 블록의 bp를 가리키도록 한다.int mm_init(void)
{
// 초기 힙 생성
if ((free_listp = mem_sbrk(8 * WSIZE)) == (void *)-1) // 8워드 크기의 힙 생성, free_listp에 힙의 시작 주소값 할당(가용 블록만 추적)
return -1;
PUT(free_listp, 0); // 정렬 패딩
PUT(free_listp + (1 * WSIZE), PACK(2 * WSIZE, 1)); // 프롤로그 Header
PUT(free_listp + (2 * WSIZE), PACK(2 * WSIZE, 1)); // 프롤로그 Footer
PUT(free_listp + (3 * WSIZE), PACK(4 * WSIZE, 0)); // 첫 가용 블록의 헤더
PUT(free_listp + (4 * WSIZE), NULL); // 이전 가용 블록의 주소
PUT(free_listp + (5 * WSIZE), NULL); // 다음 가용 블록의 주소
PUT(free_listp + (6 * WSIZE), PACK(4 * WSIZE, 0)); // 첫 가용 블록의 푸터
PUT(free_listp + (7 * WSIZE), PACK(0, 1)); // 에필로그 Header: 프로그램이 할당한 마지막 블록의 뒤에 위치하며, 블록이 할당되지 않은 상태를 나타냄
free_listp += (4 * WSIZE); // 첫번째 가용 블록의 bp
// 힙을 CHUNKSIZE bytes로 확장
if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
return -1;
return 0;
}
가용 블록 검색
asize
를 담을 수 있는 블록을 찾을 때까지 탐색을 이어간다. (First-fit)static void *find_fit(size_t asize)
{
void *bp = free_listp;
while (bp != NULL) // 다음 가용 블럭이 있는 동안 반복
{
if ((asize <= GET_SIZE(HDRP(bp)))) // 적합한 사이즈의 블록을 찾으면 반환
return bp;
bp = GET_SUCC(bp); // 다음 가용 블록으로 이동
}
return NULL;
}
할당
static void place(void *bp, size_t asize)
{
splice_free_block(bp); // free_list에서 해당 블록 제거
size_t csize = GET_SIZE(HDRP(bp)); // 현재 블록의 크기
if ((csize - asize) >= (2 * DSIZE)) // 차이가 최소 블록 크기 16보다 같거나 크면 분할
{
PUT(HDRP(bp), PACK(asize, 1)); // 현재 블록에는 필요한 만큼만 할당
PUT(FTRP(bp), PACK(asize, 1));
PUT(HDRP(NEXT_BLKP(bp)), PACK((csize - asize), 0)); // 남은 크기를 다음 블록에 할당(가용 블록)
PUT(FTRP(NEXT_BLKP(bp)), PACK((csize - asize), 0));
add_free_block(NEXT_BLKP(bp)); // 남은 블록을 free_list에 추가
}
else
{
PUT(HDRP(bp), PACK(csize, 1)); // 해당 블록 전부 사용
PUT(FTRP(bp), PACK(csize, 1));
}
}
predeccessor
와 successor
를 새로 만들어지는 가용 블록에 연결되도록 변경하고 사이즈를 남은 크기로 변경하는 방법을 사용해서 ‘가용 리스트에서 제거하고 추가하는 과정’을 줄일 수 있다. static void place(void *bp, size_t asize)
{
size_t csize = GET_SIZE(HDRP(bp)); // 현재 블록의 크기
if ((csize - asize) >= (2 * DSIZE)) // 차이가 최소 블록 크기 16보다 같거나 크면 분할
{
PUT(HDRP(bp), PACK(asize, 1)); // 현재 블록에는 필요한 만큼만 할당
PUT(FTRP(bp), PACK(asize, 1));
bp = NEXT_BLKP(bp); // 다음 블록으로 이동
PUT(HDRP(bp), PACK((csize - asize), 0)); // 남은 크기를 다음 블록에 할당(가용 블록)
PUT(FTRP(bp), PACK((csize - asize), 0));
GET_SUCC(bp) = GET_SUCC(PREV_BLKP(bp)); // 루트였던 블록의 PRED를 추가된 블록으로 연결
if (PREV_BLKP(bp) == free_listp)
{
free_listp = bp;
}
else
{
GET_PRED(bp) = GET_PRED(PREV_BLKP(bp));
GET_SUCC(GET_PRED(PREV_BLKP(bp))) = bp;
}
if (GET_SUCC(bp) != NULL) // 다음 가용 블록이 있을 경우만
GET_PRED(GET_SUCC(bp)) = bp;
}
else
{
splice_free_block(bp);
PUT(HDRP(bp), PACK(csize, 1)); // 해당 블록 전부 사용
PUT(FTRP(bp), PACK(csize, 1));
}
}
가용 리스트에서 제거
// 가용 리스트에서 bp에 해당하는 블록을 제거하는 함수
static void splice_free_block(void *bp)
{
if (bp == free_listp) // 분리하려는 블록이 free_list 맨 앞에 있는 블록이면 (이전 블록이 없음)
{
free_listp = GET_SUCC(free_listp); // 다음 블록을 루트로 변경
return;
}
// 이전 블록의 SUCC을 다음 가용 블록으로 연결
GET_SUCC(GET_PRED(bp)) = GET_SUCC(bp);
// 다음 블록의 PRED를 이전 블록으로 변경
if (GET_SUCC(bp) != NULL) // 다음 가용 블록이 있을 경우만
GET_PRED(GET_SUCC(bp)) = GET_PRED(bp);
}
가용 리스트에 추가
// 가용 리스트의 맨 앞에 현재 블록을 추가하는 함수
static void add_free_block(void *bp)
{
GET_SUCC(bp) = free_listp; // bp의 SUCC은 루트가 가리키던 블록
if (free_listp != NULL) // free list에 블록이 존재했을 경우만
GET_PRED(free_listp) = bp; // 루트였던 블록의 PRED를 추가된 블록으로 연결
free_listp = bp; // 루트를 현재 블록으로 변경
}
// 가용 리스트에서 주소 오름차순에 맞게 현재 블록을 추가하는 함수
static void add_free_block(void *bp)
{
void *currentp = free_listp;
if (currentp == NULL)
{
free_listp = bp;
GET_SUCC(bp) = NULL;
return;
}
while (currentp < bp) // 검사중인 주소가 추가하려는 블록의 주소보다 작을 동안 반복
{
if (GET_SUCC(currentp) == NULL || GET_SUCC(currentp) > bp)
break;
currentp = GET_SUCC(currentp);
}
GET_SUCC(bp) = GET_SUCC(currentp); // 루트였던 블록의 PRED를 추가된 블록으로 연결
GET_SUCC(currentp) = bp; // bp의 SUCC은 루트가 가리키던 블록
GET_PRED(bp) = currentp; // bp의 SUCC은 루트가 가리키던 블록
if (GET_SUCC(bp) != NULL) // 다음 가용 블록이 있을 경우만
{
GET_PRED(GET_SUCC(bp)) = bp;
}
}
병합
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) // 모두 할당된 경우
{
add_free_block(bp); // free_list에 추가
return bp; // 블록의 포인터 반환
}
else if (prev_alloc && !next_alloc) // 다음 블록만 빈 경우
{
splice_free_block(NEXT_BLKP(bp)); // 가용 블록을 free_list에서 제거
size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
PUT(HDRP(bp), PACK(size, 0)); // 현재 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 다음 블록 푸터 재설정 (위에서 헤더를 재설정했으므로, FTRP(bp)는 합쳐질 다음 블록의 푸터가 됨)
}
else if (!prev_alloc && next_alloc) // 이전 블록만 빈 경우
{
splice_free_block(PREV_BLKP(bp)); // 가용 블록을 free_list에서 제거
size += GET_SIZE(HDRP(PREV_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 현재 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
else // 이전 블록과 다음 블록 모두 빈 경우
{
splice_free_block(PREV_BLKP(bp)); // 이전 가용 블록을 free_list에서 제거
splice_free_block(NEXT_BLKP(bp)); // 다음 가용 블록을 free_list에서 제거
size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); // 다음 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
add_free_block(bp); // 현재 병합한 가용 블록을 free_list에 추가
return bp; // 병합한 가용 블록의 포인터 반환
}
splice_free_block
과 add_free_block
함수를 호출한다. 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) // 모두 할당된 경우
{
add_free_block(bp); // free_list에 추가
return bp; // 블록의 포인터 반환
}
else if (prev_alloc && !next_alloc) // 다음 블록만 빈 경우
{
splice_free_block(NEXT_BLKP(bp)); // 가용 블록을 free_list에서 제거
size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
PUT(HDRP(bp), PACK(size, 0)); // 현재 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 다음 블록 푸터 재설정 (위에서 헤더를 재설정했으므로, FTRP(bp)는 합쳐질 다음 블록의 푸터가 됨)
add_free_block(bp);
}
else if (!prev_alloc && next_alloc) // 이전 블록만 빈 경우
{
size += GET_SIZE(HDRP(PREV_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(bp), PACK(size, 0)); // 현재 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
else // 이전 블록과 다음 블록 모두 빈 경우
{
splice_free_block(NEXT_BLKP(bp)); // 다음 가용 블록을 free_list에서 제거
size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); // 이전 블록 헤더 재설정
PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); // 다음 블록 푸터 재설정
bp = PREV_BLKP(bp); // 이전 블록의 시작점으로 포인터 변경
}
return bp; // 병합한 가용 블록의 포인터 반환
}
‘5) 재할당하기’는 Implicit List와 동일하다.
👇🏻 Github에서 'Segregated-fits' 전체 코드 보기
🧮 Score: 86점 (이용도 46 + 처리량 40)
1) 다수의 가용 리스트 활용
2) 블록 할당
크기가 맞는 첫번째 가용 블록
을 선택한다. (First fit)3) 블록 반환
// 가용 리스트의 개수
#define SEGREGATED_SIZE (12)
// 해당 가용 리스트의 루트
#define GET_ROOT(class) (*(void **)((char *)(heap_listp) + (WSIZE * class)))
int mm_init(void)
{
// 초기 힙 생성
if ((heap_listp = mem_sbrk((SEGREGATED_SIZE + 4) * WSIZE)) == (void *)-1) // 8워드 크기의 힙 생성, heap_listp에 힙의 시작 주소값 할당(가용 블록만 추적)
return -1;
PUT(heap_listp, 0); // 정렬 패딩
PUT(heap_listp + (1 * WSIZE), PACK((SEGREGATED_SIZE + 2) * WSIZE, 1)); // 프롤로그 Header (크기: 헤더 1 + 푸터 1 + segregated list 크기)
for (int i = 0; i < SEGREGATED_SIZE; i++)
PUT(heap_listp + ((2 + i) * WSIZE), NULL);
PUT(heap_listp + ((SEGREGATED_SIZE + 2) * WSIZE), PACK((SEGREGATED_SIZE + 2) * WSIZE, 1)); // 프롤로그 Footer
PUT(heap_listp + ((SEGREGATED_SIZE + 3) * WSIZE), PACK(0, 1)); // 에필로그 Header: 프로그램이 할당한 마지막 블록의 뒤에 위치
heap_listp += (2 * WSIZE);
if (extend_heap(4) == NULL)
return -1;
if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
return -1;
return 0;
}
가용 블록 검색
get_class
함수 호출)asize
를 담을 수 있는 블록을 찾을 때까지 탐색을 이어간다. (First-fit)static void *find_fit(size_t asize)
{
int class = get_class(asize);
void *bp = GET_ROOT(class);
while (class < SEGREGATED_SIZE) // 현재 탐색하는 클래스가 범위 안에 있는 동안 반복
{
bp = GET_ROOT(class);
while (bp != NULL)
{
if ((asize <= GET_SIZE(HDRP(bp)))) // 적합한 사이즈의 블록을 찾으면 반환
return bp;
bp = GET_SUCC(bp); // 다음 가용 블록으로 이동
}
class += 1;
}
return NULL;
}
가용 리스트에서 제거
get_class
함수 호출) 해당 가용 리스트에서 제거한다.// 가용 리스트에서 bp에 해당하는 블록을 제거하는 함수
static void splice_free_block(void *bp)
{
int class = get_class(GET_SIZE(HDRP(bp)));
if (bp == GET_ROOT(class)) // 분리하려는 블록이 free_list 맨 앞에 있는 블록이면 (이전 블록이 없음)
{
GET_ROOT(class) = GET_SUCC(GET_ROOT(class)); // 다음 블록을 루트로 변경
return;
}
// 이전 블록의 SUCC을 다음 가용 블록으로 연결
GET_SUCC(GET_PRED(bp)) = GET_SUCC(bp);
// 다음 블록의 PRED를 이전 블록으로 변경
if (GET_SUCC(bp) != NULL) // 다음 가용 블록이 있을 경우만
GET_PRED(GET_SUCC(bp)) = GET_PRED(bp);
}
가용 리스트에 추가
// 적합한 가용 리스트를 찾아서 맨 앞에 현재 블록을 추가하는 함수
static void add_free_block(void *bp)
{
int class = get_class(GET_SIZE(HDRP(bp)));
GET_SUCC(bp) = GET_ROOT(class); // bp의 해당 가용 리스트의 루트가 가리키던 블록
if (GET_ROOT(class) != NULL) // list에 블록이 존재했을 경우만
GET_PRED(GET_ROOT(class)) = bp; // 루트였던 블록의 PRED를 추가된 블록으로 연결
GET_ROOT(class) = bp;
}
적합한 가용 리스트 탐색
// 적합한 가용 리스트를 찾는 함수
int get_class(size_t size)
{
if (size < 16) // 최소 블록 크기는 16바이트
return -1; // 잘못된 크기
// 클래스별 최소 크기
size_t class_sizes[SEGREGATED_SIZE];
class_sizes[0] = 16;
// 주어진 크기에 적합한 클래스 검색
for (int i = 0; i < SEGREGATED_SIZE; i++)
{
if (i != 0)
class_sizes[i] = class_sizes[i - 1] << 1;
if (size <= class_sizes[i])
return i;
}
// 주어진 크기가 마지막 클래스의 범위를 넘어갈 경우, 마지막 클래스로 처리
return SEGREGATED_SIZE - 1;
}
‘4) 블록 반환하기’와 ‘5) 재할당하기’는 Explicit List(LIFO)와 동일하다.
👇🏻 Github에서 'Buddy-System' 전체 코드 보기
🧮 Score: 79점 (이용도 39 + 처리량 40)
이 구현 방식은 힙의 메모리를 많이 차지하게 되어 mem_sbrk를 실패해 테스트 케이스를 통과하지 못한다.
아래의 결과는 config.h 파일의 MAX_HEAP을 (20*(1<<21))로 늘려주고 테스트한 결과이다.
1) 동일한 블록 크기를 갖는 다수의 가용 리스트 활용
2) 블록 할당
3) 블록 반환
buddy
가 된다.buddy
를 찾을 수 있다.buddy
가 가용 상태인지 확인해서 가용 상태라면 병합한다.buddy
가 아니라면 병합하지 않는다.SEGREGATED_SIZE
를 20으로 늘려주었다.#define SEGREGATED_SIZE (20) // segregated list의 class 개수
삭제 👉🏻 #define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
void *mm_malloc(size_t size)
{
size_t asize = 16; // 조정된 블록 사이즈
size_t extendsize; // 확장할 사이즈
char *bp;
if (size == 0) // 잘못된 요청 분기
return NULL;
/* 사이즈 조정 */
while (asize < size + DSIZE) // 요청받은 size에 8(헤더와 푸터 크기)를 더한 값을 2의 n승이 되도록 올림
{
asize <<= 1;
}
/* 가용 블록 검색 */
if ((bp = find_fit(asize)) != NULL)
{
place(bp, asize); // 할당
return bp; // 새로 할당된 블록의 포인터 리턴
}
/* 적합한 블록이 없을 경우 힙확장 */
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize / WSIZE)) == NULL)
return NULL;
place(bp, asize);
return bp;
}
할당
static void place(void *bp, size_t asize) // 할당할 위치의 bp, 사용할 양
{
splice_free_block(bp); // free_list에서 해당 블록 제거
size_t csize = GET_SIZE(HDRP(bp)); // 사용하려는 블록의 크기
while (asize != csize) // 사용하려는 asize와 블록의 크기 csize가 다르면
{
csize >>= 1; // 블록의 크기를 반으로 나눔
PUT(HDRP(bp + csize), PACK(csize, 0)); // 뒷부분을 가용 블록으로 변경
add_free_block(bp + csize); // 뒷부분을 가용 블록 리스트에 추가
}
PUT(HDRP(bp), PACK(csize, 1)); // 크기가 같아지면 해당 블록 사용 처리
}
적합한 가용 리스트 탐색
// 적합한 가용 리스트를 찾는 함수
int get_class(size_t size)
{
int next_power_of_2 = 1;
int class = 0;
while (next_power_of_2 < size && class + 1 < SEGREGATED_SIZE)
{
next_power_of_2 <<= 1;
class ++;
}
return class;
}
반환
void mm_free(void *bp)
{
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp), PACK(size, 0));
coalesce(bp);
}
병합
buddy
를 찾을 수 있다.buddy
의 블록 포인터는 현재 블록의 포인터에서 size만큼 빼거나 더해서 구할 수 있다.
size만큼 빼야 하는지 더해야 하는지 파악하려면 현재 buddy
가 왼쪽 버디인지, 오른쪽 버디인지 알아야 한다.
현재 블록의 주소에서 메모리 블록이 시작하는 주소를 빼고, 그 값이 블록의 사이즈를 비트로 나타낸 것과 비교했을 때 겹치는 비트가 있다면 현재 블록은 오른쪽 buddy
에 해당한다.
static void *coalesce(void *bp)
{
add_free_block(bp); // 현재 블록을 free list에 추가
size_t csize = GET_SIZE(HDRP(bp)); // 반환할 사이즈
void *root = heap_listp + (SEGREGATED_SIZE + 1) * WSIZE; // 실제 메모리 블록들이 시작하는 위치
void *left_buddyp; // 왼쪽 버디의 bp
void *right_buddyp; // 오른쪽 버디의 bp
while (1)
{
// 좌우 버디의 bp 파악
if ((bp - root) & csize) // 현재 블록에서 힙까지의 메모리 합(bp - root)과 csize가 중복되는 비트가 있다면 현재 블록은 오른쪽 버디에 해당
{
left_buddyp = bp - csize;
right_buddyp = bp;
}
else
{
right_buddyp = bp + csize;
left_buddyp = bp;
}
// 양쪽 버디가 모두 가용 상태이고, 각 사이즈가 동일하면 (각 버디가 분할되어있지 않으면)
if (!GET_ALLOC(HDRP(left_buddyp)) && !GET_ALLOC(HDRP(right_buddyp)) && GET_SIZE(HDRP(left_buddyp)) == GET_SIZE(HDRP(right_buddyp)))
{
splice_free_block(left_buddyp); // 양쪽 버디를 모두 가용 리스트에서 제거
splice_free_block(right_buddyp);
csize <<= 1; // size를 2배로 변경
PUT(HDRP(left_buddyp), PACK(csize, 0)); // 왼쪽 버디부터 size만큼 가용 블록으로 변경
add_free_block(left_buddyp); // 가용 블록으로 변경된 블록을 free list에 추가
bp = left_buddyp;
}
else
break;
}
return bp;
}
‘5) 재할당하기’는 Explicit List(LIFO)와 동일하다.
Github에서 'Simple-Segregated Storage' 전체 코드 보기
🧮 Score: 70점 (이용도 30 + 처리량 40)
Github에서 'Address-Ordered' 전체 코드 보기
🧮 Score: 82점 (이용도 46 + 처리량 36)
Github에서 'Size-Ordered' 전체 코드 보기
🧮 Score: 86점 (이용도 46 + 처리량 40)