Malloc Lab을 진행하면서 나는 92점이라는 점수를 받았다. 이 점수는 분리 가용 리스트(Segregated Free List)를 최초로 적용했을 때는 86점 수준이었으며, 이후 realloc 개선과 자잘한 최적화를 통해 92점까지 올릴 수 있었다. 하지만 이 점수가 내 코드의 한계인지, 더 높은 점수를 받을 수 있는 여지가 있는지 확인하고 싶었다. 그래서 다양한 개선 방안을 탐색하고, 실제로 적용하기 위해 시도했던 모든 과정을 공유하고자 이 주제를 선정하게 되었다.
🔍 분리 가용 리스트 적용 직후
아래는 분리 가용 리스트를 적용한 직후 얻은 점수이다.
🔧 최적화 이후 결과
realloc의 로직을 개선하고, 최적화를 반복적으로 적용하였다. 그 결과 아래와 같이 성능은 상당히 개선되었다
📈 요청 크기 히스토그램 분석
성능 최적화를 위해 먼저 어떤 크기의 malloc 요청이 많이 발생하는지 파악할 필요가 있었다. 이를 위해 아래와 같은 방식으로 mm.c 파일에 히스토그램 로직을 삽입했다.
1단계: 요청 크기 기록용 배열 추가
mm.c 파일 상단에 다음과 같이 전역 변수와 범위 정의를 추가했다.
#define MAX_HISTOGRAM_SIZE 1024 // 1KB까지의 요청 크기만 기록 (원하면 늘릴 수 있음)
static int size_histogram[MAX_HISTOGRAM_SIZE];
2단계: mm_malloc 내부에 기록 코드 삽입
mm_malloc 함수 내부에서 size를 기록하는 부분을 추가했다:
void *mm_malloc(size_t size)
{
if (size < MAX_HISTOGRAM_SIZE) {
size_histogram[size]++;
}
...
이렇게 하면 매번 mm_malloc(size)가 호출될 때마다 해당 size 요청 횟수가 기록된다.
3단계: 기록된 히스토그램 출력 함수 추가
void print_malloc_histogram()
{
printf("\n[malloc size histogram]\n");
for (int i = 0; i < MAX_HISTOGRAM_SIZE; i++) {
if (size_histogram[i] > 0) {
printf("Size %3d bytes: %d times\n", i, size_histogram[i]);
}
}
printf("[end of histogram]\n\n");
}
결과
[malloc size histogram]
Size 9 bytes: 48 times
Size 16 bytes: 384 times
Size 24 bytes: 12 times
Size 48 bytes: 48 times
Size 64 bytes: 492 times
Size 112 bytes: 1500 times
Size 128 bytes: 1680 times
Size 160 bytes: 72 times
Size 448 bytes: 2688 times
Size 456 bytes: 240 times
Size 512 bytes: 3096 times
[end of histogram]
출력 결과를 보면 사이즈가 112, 128, 448, 512 인 구간에 1000회 이상의 요청이 몰려 있는 것을 확인할 수 있었다.
get_class 최적화 전략
초기에는 아래처럼 2배 단위로 클래스 분류를 수행하였다.
static int get_class(size_t size){
int class = 0;
size >>= 4; // 최소 16부터 시작 (DSIZE)
while (size > 1 && class < SEGREGATED_SIZE - 1) {
size >>= 1;
class++;
}
return class;
}
하지만 요청이 집중되는 크기에 대해 이 방식은 공간 낭비와 탐색 오버헤드를 초래할 수 있었다. 이에 따라 수작업 기반의 튜닝을 적용하였다.
수작업 튜닝 기반 분류
#define SEGREGATED_SIZE (16)
static int get_class(size_t size){
if (size <= 16) return 0;
if (size <= 32) return 1;
if (size <= 48) return 2;
if (size <= 64) return 3;
if (size <= 96) return 4;
if (size <= 112) return 5;
if (size <= 128) return 6;
if (size <= 160) return 7;
if (size <= 256) return 8;
if (size <= 384) return 9;
if (size <= 448) return 10;
if (size <= 512) return 11;
if (size <= 1024) return 12;
if (size <= 2048) return 13;
if (size <= 4096) return 14;
return 15; // 그 외 초대형 블록
}
이렇게 하여 요청이 집중되는 112, 128, 448, 512 바이트를 개별 클래스로 직접 분류하였다.
무식한 방법이긴 하지만 이렇게 하면 특정 사이즈의 요청을 빠르게 처리할 수 있지 않을까? 하는 생각이 들었다.
결과는 위와 같았다. 점수는 같았지만 총 실행 시간이 엄청난 차이가 있었다.
항목 | 적용 전 | 적용 후 | 변화 |
Util (메모리 효율) | 52 점 | 52 점 | 동일 |
Thru (처리량) | 40 점 | 40 점 | 동일 |
Total | 92 점 | 92 점 | 동일 |
총 실행 시간 | 0.093752초 | 0.002696초 | 34배 빨라짐 |
Trace 8 Kops | 262 → 84418 | 극적인 향상 | 중요한 개선 가능성 있음 |
Trace 8 분석 결과
- binary2-bal.rep에서 112 바이트, 128 바이트 요청이 집중적으로 발생
- 중반부에는 해당 요청이 해제되고
- 후반부에는 새로운 크기의 할당 요청이 이어짐
따라서 특정 크기의 블록 요청에 맞춘 분리 리스트 구성은 속도 향상에는 분명히 도움이 되었으나, 메모리 효율 개선에는 영향이 없었기 때문에 점수 향상으로 이어지지 않았다.
🚧 나는 왜 92점을 넘지 못했는가?
내가 최종적으로 얻은 점수는 92점이었고, 그 이상의 점수는 다양한 개선을 시도하더라도 넘기 어려웠다. 그 이유는 다음과 같이 분석했다.
- 환경의 차이 (64-bit 시스템)
- 포인터와 메타데이터가 2배 크기이기 때문에, 할당 가능한 유효 메모리가 줄어들며
- 벤치마크는 힙 확장 한도를 넘기면 즉시 오류 처리한다.
- 리스트 개수 증가로 인한 오버헤드
- 클래스 수가 많아지면 루트 포인터만으로도 큰 메모리 공간이 소모된다.
- 예를 들어, 16개 클래스는 160바이트 추가 헤더 공간을 요구한다.
- 진정한 성능 개선은 32-bit에서 검증 필요
- 더 많은 실험을 위해선 memlib 수정 또는 32-bit 환경 도커 테스트가 필요할 것이다.
결론
점수는 같았지만, 실행 시간은 수십 배 빨라졌고, 특정 트레이스에 대한 처리량이 극적으로 향상되었다. 이런 무식해 보일 수 있는 시도들이 실제로 성능에 영향을 줄 수 있음을 확인하였다. 다만, Malloc Lab의 점수 체계는 속도보다 메모리 효율에 높은 비중을 두기 때문에 점수 향상을 위해서는 효율을 해치지 않으면서도 속도까지 잡는 정교한 전략이 필요함을 느꼈다.
'크래프톤 정글' 카테고리의 다른 글
[WebProxy-Lab] proxy 서버 구현하기 Part.2 - 프록시 서버 구현 (1) | 2025.05.05 |
---|---|
[WebProxy-Lab] proxy 서버 구현하기 Part.1 - 요구사항 확인 및 설계 (2) | 2025.05.05 |
[CS] 이더넷(Ethernet) (2) | 2025.04.28 |
[CS] DMA(Direct Memory Access) (0) | 2025.04.28 |
[CS] Demand-Zero Memory (1) | 2025.04.28 |