오늘만 푸는 초강력 가상 메모리 프롬프트 [CSAPP Chap 9]

ㄴㄴ 그런거 없음. 가상 메모리를 기깔나게 관리하는건 좋지만, 왠만하면 우리는 혼자 일하는게 아니다. 유지보수성, 가독성, 다양한걸 고려해야하고, 그래서 트레이드오프를 논하는 것이다.


가상 메모리 친화적 코딩하기

  • 주의!! 이걸 다 지켜서 프로그램을 만들었다면 그 프로그램은 미래가 없다. 이건 항상 따라야 할 코딩 표준이 아니니, 진짜 문제가 있는 범위에 신중히 쓰자.

 

페이지 단위 접근 최적화

: 데이터 접근이 4KB 페이지 경계를 넘지 않도록 설계한다.

단점

  • 극심한 코드 복잡도 증가: 프로그래머가 논리적 데이터 구조가 아닌, 하드웨어의 물리적 페이지 경계라는 임의의 기준에 맞춰야한다.
  • 시스템 의존성: 페이지 크기는 시스템 아키텍처나 설정에 따라 달라질 수 있다. 4KB에 하드코딩된 최적화는 다른 환경에서 무용지물이 되거나 오히려 성능이 떨어질 수 있다.
  • 유지보수 못함: 코드를 읽는 사람이 "왜 여기서 갑자기 데이터 구조를 쪼갰지?"라는 의도를 파악하기 정말 힘들다.

 

순차적 메모리 접근

랜덤 접근을 피하고 순차 접근(배열, 리스트)을 사용한다.

단점

  • 알고리즘의 제약: 문제 해결에 가장 적합한 자료구조가 랜덤 접근 일 수 있다. 공간 지역성만을 보고 더 “빨리 해결 할 수 있는” 영역을 놓고 있는 것은 또 다른 문제를 만들 수 있다.
  • 자료구조의 근본 설계: 캐시 효율이 좋은 배열형태의 자료구조와 삽입/삭제가 유연한 포인터 조작 위주의 자료구조는 근본적인 지향점이 다르다. 이는 VM 친화성 이전에 어떤 연산이 더 빈번한가에 따라 결정되어야 할 문제이다.

 

데이터 구조 정렬

자주 쓰는 필드를 앞에 배치하고 캐시 라인/페이지 경계에 맞게 정렬한다.

단점

  • 논리적 그룹화 훼손: 프로그래머는 보통 관련 있는 데이터끼리 필드를 묶어서 구조체를 설계할 것이다. (예: position, rotation, scale / hp, mp, stamina). 성능 최적화를 위해 이 논리적 순서를 뒤섞으면(예: hp, position, mp, scale...) 코드의 가독성과 논리적 무결성이 심각하게 훼손된다.
  • 유지보수성 저하: 새로운 팀원이 이 구조를 모르고 필드를 '논리적으로 올바른' 위치에 추가하는 순간, 기존의 최적화는 처음부터 다시 해야한다.
  • 과도한 패딩: 페이지 경계에 맞추는 것은 극단적인 경우이며, 대부분은 캐시 라인(64바이트)에 맞춘다. 하지만 이마저도 구조체 사이에 의도적인 패딩을 추가해야 하므로, 전체적인 메모리 사용량이 오히려 늘어 날 수 있다.

 

메모리 풀링 및 재사용

동적 할당(malloc/free, new/delete)을 피하고 메모리 풀을 사용한다.

단점

  • 메모리 낭비: 메모리 풀은 '최대 사용량'을 예측하여 미리 큰 덩어리를 할당해 둔다. 프로그램이 해당 풀을 거의 사용하지 않는 순간에도 이 메모리는 계속 점유되므로, 프로그램의 전체적인 피크 메모리 사용량이 증가할 수 있다.
  • 그래도, 유니티에선 얘가 제일 만만하다. 심지어 공식 API까지 제공되는데?

 

TLB 친화적 배열 분할 (블로킹)

큰 배열을 작은 블록으로 나눠 페이지 경계 안에 유지한다.

단점

  • 알고리즘 복잡도 증가: 가장 대표적인 예가 행렬 곱셈인데, 단순 3중 for문으로 구현할 수 있는 로직을, 캐시/TLB 블로킹을 적용하면 6중 for문으로 바뀌는 등 코드의 복잡도가 미치게 어려워진다.
  • 매직 넘버 의존성: '최적의 블록 크기'는 L1 캐시, L2 캐시, TLB 엔트리 수에 따라 달라진다. 즉, 이 코드는 특정 CPU 아키텍처에 과도하게 튜닝된다. 내 Intel에서 최적이었던 코드가 AMD나 ARM에서는 오히려 메롱이 될 수 있다.

 

메모리 접근 패턴 분석

병목 지점을 프로파일링하고 리팩토링한다.

얘는 단점이 없다. 앞 내용을 하기전에 반드시 해야한다. 렉이 걸린다고 Lobby부터 기깔나게 최적화 들어가면 바로 이제 어줍잖은 딜레마에 빠지게 될것이다. 경험담임


메모리 버그 방지하기

  • 여긴 알아두면 무조건 좋다.

nullptr 접근 금지

  • 가상 메모리에서 주소 0은 일반적으로 매핑되지 않는다.
  • int* ptr = NULL; *ptr = 10; 이딴거 하면 박살남

 

버퍼 오버런 / 언더런

  • 배열 경계를 벗어난 접근은 다른 페이지를 침범하거나 매핑되지 않은 영역에 접근하게 된다.
  • char buf[10]; buf[20] = 'x'; → 다른 페이지에 쓰기 시도 → Seg Fault

 

유효하지 않은 포인터 역참조

  • 이미 해제된 메모리를 접근하면 박살남
  • free(ptr); *ptr = 5; → 해제 후 사용 시도 → 펑

 

스택 오버플로우

  • 너무 큰 지역 변수 사용, 재귀 함수 남용 등의 사유
  • 스택에 대한 제한을 초과하면 OS 주관으로 즉시 프로세스에 크래시 발생

 

mmap 사용 시 경계 확인

  • 직접 메모리 매핑 시, 페이지 크기 단위로 정렬 하기
  • mmap(addr, size, ...) 에서 addr 이 페이지 경계에 맞지 않으면 실패하거나 오작동

 

TLB thrashing 유도

  • 큰 배열을 랜덤하게 접근하면 TLB 캐시가 자주 교체되어 성능 저하 발생
  • 랜덤 접근이 의도된게 아니라면, 순차 접근, 구조화된 데이터를 사용하기

 

메모리 보호 위반

  • 읽기 전용 페이지에 시도 → Seg Fault
  • const char* msg = "hello"; msg[0] = 'H'; → 리터럴은 읽기전용 영역에 위치한다

 

사실 만들고 싶어서 만드는 버그가 어디있겠는가? 우린 알고도 못지키는 경우가 있지만, 의식적으로 기억해보자.

'컴퓨터 이론 > CS:APP' 카테고리의 다른 글

게임과 앱 개발에 중요한 [CSAPP Chap 9]  (0) 2025.10.23
예외처리 #3 [CSAPP Chap 8]  (0) 2025.10.20
예외처리 #2 [CSAPP Chap 8]  (0) 2025.10.20
예외처리 #1 [CSAPP Chap 8]  (0) 2025.10.20
링커 #2 [CSAPP Chap 7]  (0) 2025.10.20