JUNGLE 240426 ~ 240429 (W06)
KRAFTON JUNGLE Week 06
Malloc Lab
⭐️ 점수 요약
- Implicit List
- First Fit : 44 (util) + 13 (thru) = 58/100
- Next Fit : 44 (util) + 40 (thru) = 84/100
- Best Fit : 45 (util) + 12 (thru) = 57/100
- Explicit List : 42 (util) + 40 (thru) = 82/100
- Segregated List : 44 (util) + 40 (thru) = 84/100
Realloc
함수 최적화 : 50 (util) + 40 (thru) = 90/100
아래 포스팅된 코드는 최종 코드이다. (Segregated List + Realloc
함수 최적화)
mem_sbrk
void *mem_sbrk(int incr)
{
char *old_brk = mem_brk; // 이전에 설정된 브레이크 포인터 값을 저장
if ( (incr < 0) || ((mem_brk + incr) > mem_max_addr)) { // incr이 0보다 작거나 incr을 더한 값이 최대 주소보다 크면
errno = ENOMEM; // 에러 반환
fprintf(stderr, "ERROR: mem_sbrk failed. Ran out of memory...\n");
return (void *)-1;
}
mem_brk += incr; // 그렇지 않은 경우 현재 brk 증가
return (void *)old_brk; // 이전 브레이크 포인터 주소 반환
}
old_brk
를 반환하는 이유?
old_brk
를 반환함으로써 호출자는 힙이 얼마나 확장되었는지와 새로운 영역의 시작 주소를 알 수 있게 됨- 즉, 이전
brk
가 가리키던 영역부터 새로운 영역의 시작까지의 범위를 알려줌- 또한, 새로운 힙 영역은
old_brk
부터 시작됨
mm.c 기본 함수
mm_init
- 초기 힙 영역을 확보하고 블록 생성하는 함수
- 할당기를 초기화하고 성공이면 0, 아니면 -1 리턴
sbrk
함수를 호출하여 힙 영역 확보- 프롤로그 블록: header, footer로 구성된 8 byte 할당 블록
- 에필로그 블록: header로 구성된 0 byte 할당 블록
int mm_init(void)
{
for (int i = 0; i <= SEGSIZE; i++) // Seg_list 초기화
{
seg_free_lists[i] = NULL;
}
if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1) // 오류 발생 시 return -1
return -1;
PUT(heap_listp, 0); // Padding (8의 배수로 정렬하기 위해 패딩 블록 할당)
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 += DSIZE; // Prologue의 Footer와 Epilogue의 Header 사이에 포인터 위치
if (extend_heap(CHUNKSIZE / WSIZE) == NULL) // CHUNK SIZE를 워드 단위로 환산해서 brk 확장
return -1;
return 0;
}
extend_heap
brk
포인터로 힙 영역을 확장하는 함수 (확장 단위는 CHUNK SIZE)mm_init
에서 초기 힙 영역을 확보했다면, 블록이 추가될 때 힙 영역을 확장해주는 역할
static void *extend_heap(size_t words)
{
char *bp;
size_t size;
size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE; // 워드 사이즈가 홀수라면 1을 더하고 짝수라면 그대로 WSIZE를 곱해서 사이즈 변환
if ((bp = mem_sbrk(size)) == (void *)-1) // mem_sbrk로 힙 확보가 가능한지 여부를 체크
return NULL;
PUT(HDRP(bp), PACK(size, 0)); // Block Header -> free(0) 설정
PUT(FTRP(bp), PACK(size, 0)); // Block Footer -> free(0) 설정
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); // New Epilogue Header
return coalesce(bp); // 블록 연결 함수 호출
}
mm_malloc
- 요청한 사이즈만큼 메모리 블록을 가용 블록으로 확보하고 해당 payload 주소값을 리턴하는 함수
void *mm_malloc(size_t size)
{
size_t allocated_size; // 블록 사이즈 조정
size_t extend_size; // heap에 맞는 fit이 없다면 확장하기 위한 사이즈
char *bp;
if (size == 0)
return NULL;
if (size <= DSIZE) // 할당할 크기가 8 byte보다 작으면 allocated size에 16 byte를 담음
allocated_size = 2 * DSIZE;
else // 할당할 크기가 8 byte보다 크면 allocated size를 8의 배수로 맞춰줘야 함
allocated_size = ALIGN(size + DSIZE); // (할당할 크기 + 8 bytes (Header + Footer))를 8의 배수로 맞춰주는 작업
if ((bp = find_fit(allocated_size)) != NULL) // find_fit 함수로 할당된 크기를 넣을 공간이 있는지 체크
{
place(bp, allocated_size); // 가능하다면 분할 후 할당
return bp;
}
extend_size = MAX(allocated_size, CHUNKSIZE); // 넣을 공간이 없다면, 새 가용블록으로 힙을 확장
if ((bp = extend_heap(extend_size / WSIZE)) == NULL) // 확장할 공간이 없다면 NULL 반환
return NULL;
place(bp, allocated_size); // 확장이 됐다면, 공간을 할당하고 분할
return bp;
}
mm_free
- 할당된 블록을 메모리 해제하는 함수
void mm_free(void *bp) { size_t size = GET_SIZE(HDRP(bp)); // 해당 블록의 사이즈 저장 PUT(HDRP(bp), PACK(size, 0)); // 해당 블록의 Header -> free(0) 설정 PUT(FTRP(bp), PACK(size, 0)); // 해당 블록의 Footer -> free(0) 설정 coalesce(bp); // free 블록 연결 함수 호출 }
coalesce
- 가용 블록 병합 함수
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)); // Case 1: prev 할당 불가(1) & next 할당 불가(1) // Case 2: prev 할당 불가(1) & next 할당 가능(0) if (prev_alloc && !next_alloc) { delete_node(NEXT_BLKP(bp)); // 다음 블록을 리스트에서 삭제 size += GET_SIZE(HDRP(NEXT_BLKP(bp))); // 현재 블록 사이즈에 다음 블록 사이즈를 더함 PUT(HDRP(bp), PACK(size, 0)); // 현재 블록의 Header에 새로운 사이즈와 free(0) 설정 PUT(FTRP(bp), PACK(size, 0)); // 현재 블록의 Footer에 새로운 사이즈와 free(0) 설정 } // Case 3: prev 할당 가능(0) & next 할당 불가(1) else if (!prev_alloc && next_alloc) { delete_node(PREV_BLKP(bp)); // 이전 블록을 리스트에서 삭제 size += GET_SIZE(HDRP(PREV_BLKP(bp))); // 현재 블록 사이즈에 이전 블록 사이즈를 더함 bp = PREV_BLKP(bp); // bp를 이전 블록의 위치로 변경 PUT(HDRP(bp), PACK(size, 0)); // 현재 블록의 Header에 새로운 사이즈와 free(0) 설정 PUT(FTRP(bp), PACK(size, 0)); // 현재 블록의 Footer에 새로운 사이즈와 free(0) 설정 } // Case 4: prev 할당 가능(0) & next 할당 가능(0) else if (!prev_alloc && !next_alloc) { delete_node(PREV_BLKP(bp)); // 이전 블록을 리스트에서 삭제 delete_node(NEXT_BLKP(bp)); // 다음 블록을 리스트에서 삭제 size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp))); // 현재 블록 사이즈에 이전 블록 사이즈와 다음 블록 사이즈를 더함 bp = PREV_BLKP(bp); // bp를 이전 블록의 위치로 변경 PUT(HDRP(bp), PACK(size, 0)); // 현재 블록의 Header에 새로운 사이즈와 free(0) 설정 PUT(FTRP(bp), PACK(size, 0)); // 현재 블록의 Footer에 새로운 사이즈와 free(0) 설정 } insert_node(bp); // 병합된 블록을 list에 추가 return bp; }
place
- 확보된 데이터 블록에 실제로 데이터 사이즈를 반영해서 할당 처리하는 함수
- 위치와 사이즈를 인자로 받아서 영역 낭비를 최소화 -> 남는 블록은 분할
static void *place(void *bp, size_t allocated_size)
{
size_t curr_size = GET_SIZE(HDRP(bp));
delete_node(bp); // 현재 블록 삭제
// Case 1. 남는 부분이 최소 블록 크기(16 bytes) 이상일 때 -> 블록 분할
if ((curr_size - allocated_size) >= (2 * DSIZE)) // 남는 블록이 최소 블록 크기(16 bytes) 이상일 때
{
PUT(HDRP(bp), PACK(allocated_size, 1)); // Header -> size와 allocated(1) 설정
PUT(FTRP(bp), PACK(allocated_size, 1)); // Footer -> size와 allocated(1) 설정
bp = NEXT_BLKP(bp); // bp 위치를 다음 블록 위치로 갱신
// 블록 분할
PUT(HDRP(bp), PACK(curr_size - allocated_size, 0)); // 남은 공간의 Header -> 남는 size와 free(0) 설정
PUT(FTRP(bp), PACK(curr_size - allocated_size, 0)); // 남은 공간의 Footer -> 남는 size와 free(0) 설정
insert_node(bp); // 분할된 (남은 가용) 블록을 free list에 삽입
}
// Case 2. 남는 부분이 최소 블록 크기(16 bytes) 미만일 때 -> 블록 분할 필요 X
else
{
PUT(HDRP(bp), PACK(curr_size, 1)); // 분할할 필요가 없다면, 그냥 할당
PUT(FTRP(bp), PACK(curr_size, 1)); // 남는 가용 블록이 없으므로 insert_node 호출할 필요 없음
}
return bp;
}
find_fit
- 할당할 블록을 찾는 함수
- Segregated List에서 알맞은 인덱스를 찾아서 가용 블록 탐색
static void *find_fit(size_t asize)
{
char *bp;
int index = find_index(asize);
for (int i = index; i <= SEGSIZE; i++)
{
for (bp = seg_free_lists[i]; bp != NULL; bp = SUCC_FREEP(bp)) // 해당 인덱스의 리스트에서 적합한 가용 블록을 탐색하는 과정
{
if (GET_SIZE(HDRP(bp)) >= asize) // 크기가 맞는 블록을 찾으면
return bp; // bp 반환
}
}
return NULL; // 알맞은 크기가 없다면 NULL 반환
}
기존의 realloc
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 = GET_SIZE(HDRP(oldptr)); // 이전 블록 사이즈를 copySize에 저장
if (size < copySize) // 요청한 size가 원래 크기보다 작다면,
copySize = size; // 기존 메모리 블록에서 일부만 복사해야 하므로 copySize를 요청한 크기로 설정
memcpy(newptr, oldptr, copySize); // 이전 블록에서 copySize만큼의 데이터를 새 블록에 복사
mm_free(oldptr); // 기존 블록 free
return newptr;
}
변경된 realloc
- 기존의 함수가 무조건 새로운 메모리를 할당했다면, 변형된
realloc
함수에서는 두가지 경우를 걸러줌으로써 자원 활용도를 높인다.- 원래 사이즈가 새롭게 할당된 사이즈보다 클 때 -> 블록 분할
- 원래 사이즈보다 새롭게 할당된 사이즈가 더 클 때 -> 뒷 블록 체크해서 블록 병합
- 그렇다면.. 이전 블록은 왜 체크를 안하는가? -> payload를 복사해줘야 하기 때문에 비효율적임!!
- 44 (util) + 40 (thru) = 84/100 -> (
realloc
변경 후) 50 (util) + 40 (thru) = 90/100
void *mm_realloc(void *ptr, size_t size)
{
if (size == 0)
{
mm_free(ptr);
return NULL;
}
if (ptr == NULL)
return mm_malloc(size); // ptr이 NULL이면 새로운 메모리 할당
void *oldptr = ptr; // 기존의 메모리 주소 저장
void *newptr = oldptr; // newptr에 oldptr 저장
size_t old_size = GET_SIZE(HDRP(oldptr)); // 원래 사이즈
size_t allocated_size = ALIGN(size + DSIZE); // + DSIZE (Header + Footer)
if (old_size >= allocated_size) // Case 1: 기존 블록의 크기가 새로운 사이즈보다 클 때
{
if (old_size - allocated_size >= 2 * DSIZE) // 나머지 크기가 최소 블록 크기보다 크다면
{
PUT(HDRP(oldptr), PACK(allocated_size, 1)); // 블록 분할
PUT(FTRP(oldptr), PACK(allocated_size, 1));
oldptr = NEXT_BLKP(oldptr);
PUT(HDRP(oldptr), PACK(old_size - allocated_size, 0));
PUT(FTRP(oldptr), PACK(old_size - allocated_size, 0));
insert_node(oldptr); // 리스트에 남은 블록 추가
}
return newptr;
}
size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(oldptr))); // 다음 블록의 할당 여부
size_t combined_next = old_size + GET_SIZE(HDRP(NEXT_BLKP(oldptr))); // 현재 블록과 다음 블록을 합친 크기
if (!next_alloc && combined_next >= allocated_size) // Case 2: 다음 블록이 가용 상태이고 combined_size가 할당해야 할 크기보다 크다면
{
delete_node(NEXT_BLKP(oldptr)); // 다음 블록 리스트에서 삭제
PUT(HDRP(oldptr), PACK(combined_next, 1)); // 가용 블록 합체
PUT(FTRP(oldptr), PACK(combined_next, 1));
return newptr;
}
newptr = mm_malloc(size); // 위 조건이 충족되지 않으면 새로운 메모리 블록 할당
if (!newptr)
return NULL;
memmove(newptr, oldptr, old_size - DSIZE); // 예전 블록에서 새 블록으로 데이터 복사
mm_free(oldptr); // 예전 블록 메모리 해제
return newptr;
}
Segregated List
- 다수의 가용 리스트를 유지하며, 각각의 리스트는 거의 동일한 크기의 블록들을 저장
- 모든 가능한 블록 크기를 동일 클래스의 집합들로 분리하는 방식
- 블록의 크기 단위는 2의 n제곱 단위로 한다.
find_index
- Seg_List Index를 찾는 함수
- Index를 찾는 방법!!
- size가 분할이 가능하다는 것은 shift 연산이 가능하다는 뜻
- shift 연산이 불가능할 때까지 size / 2를 계산해서 index를 하나씩 올려준다.
- 2의 n제곱으로 분리해서 리스트를 관리하는 방식
static int find_index(size_t size)
{
int index = 0;
while ((index < SEGSIZE - 1) && (size > 1))
{
size >>= 1;
index++;
}
return index;
}
insert_node
- Seg_List에 노드를 삽입하는 함수
static void insert_node(void *bp)
{
int index = find_index(GET_SIZE(HDRP(bp))); // 리스트의 index
// Case 1: 비어있는 리스트에 추가하는 경우
if (seg_free_lists[index] == NULL)
{
PRED_FREEP(bp) = NULL; // bp의 전임자는 NULL
SUCC_FREEP(bp) = NULL; // bp의 후임자는 NULL
}
// Case 2: 그 외
else
{
PRED_FREEP(bp) = NULL; // bp의 전임자는 NULL
SUCC_FREEP(bp) = seg_free_lists[index]; // 후임자는 기존의 첫번째 블록
PRED_FREEP(seg_free_lists[index]) = bp; // 양방향 연결
}
seg_free_lists[index] = bp; // 리스트 포인터를 bp로 변경
}
delete_node
- Seg_List에 노드를 삭제하는 함수
static void delete_node(void *bp)
{
int index = find_index(GET_SIZE(HDRP(bp))); // 리스트의 index
if (seg_free_lists[index] == bp)
{
// Case 1: 리스트의 유일한 블록을 삭제하는 경우
if (SUCC_FREEP(bp) == NULL)
seg_free_lists[index] = NULL; // 리스트 포인터 NULL 처리
// Case 2: 리스트의 첫번째 블록을 삭제하는 경우
else
{
PRED_FREEP(SUCC_FREEP(bp)) = NULL; // 후임자의 앞 주소 NULL 처리
seg_free_lists[index] = SUCC_FREEP(bp); // 리스트 포인터는 후임자로 설정
}
}
else
{
// Case 3: 리스트의 마지막 블록을 삭제하는 경우
if (SUCC_FREEP(bp) == NULL)
SUCC_FREEP(PRED_FREEP(bp)) = NULL; // 전임자의 뒷 주소 NULL 처리
// Case 4: 중간에 위치한 블록을 삭제하는 경우
else
{
PRED_FREEP(SUCC_FREEP(bp)) = PRED_FREEP(bp); // 후임자의 앞 주소를 전임자로 설정
SUCC_FREEP(PRED_FREEP(bp)) = SUCC_FREEP(bp); // 전임자의 뒷 주소를 후임자로 설정
}
}
}
그 외 Implicit (First Fit, Next Fit, Best Fit), Explicit 방식은 여기로!!