티스토리 뷰
MemoryProject GC 설계 일지
주제: 메모리 할당 모델 변경, 컴파일 방화벽 트레이드오프, 그리고 API 구조 최적화
1. 메모리 할당 모델: "Binned" 폐기 및 "Bump Allocator" 채택
객체와 Accessor의 메모리 배치 방식과 그에 따른 할당 전략을 재검토했습니다.
- 기존 계획: UE 스타일의
Binned Allocator(고정 크기 풀) 사용. - 제기된 문제점:
- "근본적 모순": 우리가 선택한
[Accessor | T]연속 블록 모델은T마다 크기가 다른 '가변 크기(Variable Size)' 할당을 요구합니다. 이는 고정 크기 풀(Pool_32,Pool_64...)과는 구조적으로 호환되지 않습니다.
- "근본적 모순": 우리가 선택한
- 논의된 대안:
- Next-Fit (순환 탐색): 빈 공간을 찾아 리스트를 순회함. -> 할당 속도가 느림(O(N)).
- Free List: 빈 공간 목록 관리. -> 단편화 및 관리 오버헤드.
- 최종 결정: Bump Allocator (순차 할당)
- 전략: 평소에는
ptr += size로 O(1)의 초고속 할당을 수행하고, 빈 공간(구멍)은 무시합니다. - 해결: 메모리가 꽉 차면 GC(Compaction)를 트리거하여 구멍을 없애고
ptr를 초기화합니다. 이것이 현대 GC의 표준 모델임을 확인했습니다.
- 전략: 평소에는
2. 컴파일 방화벽 vs 런타임 동작 (The Great Trade-off)
Memory.h의 헤더 의존성을 줄이면서(컴파일 속도), 템플릿 객체 T를 올바르게 생성/소멸시키는 딜레마를 해결했습니다.
- 딜레마 상황:
IPoolManager를 인터페이스로 만들면Allocate<T>템플릿을 가질 수 없습니다. (가상 함수는 템플릿 불가)IPoolManager가void*만 다루게 하면(Type Erasure),Sweep단계에서~T()소멸자를 호출할 방법이 없습니다.
- 해결 시도 (C안):
- UE처럼
TypeInfo에 소멸자 함수 포인터를 담아 전달하는 방식. -> Reflection 모듈 수정이 필요하여 기각.
- UE처럼
- 최종 결정: "책임 분리 (B안)"
Memory.h(API):MakePtr<T>템플릿 구현을 포함하여 헤더가 무거워지는 것을 감수합니다.MakePtr가T의 생성(placement new)과 예외 처리(try-catch)를 전담합니다.IPoolManager(구현):AllocateBlock(size)만 수행하는 '멍청한' 인터페이스로 유지하여 컴파일 방화벽을 지킵니다.Accessor<T>(템플릿):virtual Release()함수가~T()소멸자 호출을 책임져 런타임 안전성을 확보합니다.
3. API 설계 및 순환 참조(Circular Dependency) 해결
RootPtr, MemoryCore, Memory 간의 복잡한 포함 관계를 정리했습니다.
- 제기된 문제점:
- "
Memory.h가RootPtr.h를 포함하고,RootPtr생성자가Memory::GetCollector()를 호출하기 위해 다시Memory.h를 필요로 하는 순환 참조가 발생한다." - "전역
RootPtr생성 시 GC 싱글톤이 초기화되지 않아 크래시가 발생할 수 있다 (SIOF)."
- "
- 최종 결정: 계층형 헤더 구조 (Layered Architecture)
MemoryCore.h(Layer 0): 핵심 API(MakePtr,GetCollector선언) 및 기본 타입.RootPtr를 모름.RootPtr.h(Layer 1):MemoryCore.h를 포함하여 GC에 접근.Memory.h(Layer 2): 사용자를 위한 마스터 헤더. 위 두 헤더를 모두 포함.
- 안전성 확보:
MemoryCore.cpp내부에서 Meyers' Singleton (static instance)을 사용하여 초기화 순서 문제를 원천 차단했습니다.
4. Pool 구현 상세 및 Accessor 활용
- 제기된 문제점:
- "Pool은
Accessor를 모르는 게(캡슐화) 좋지 않나?" - "Pool이 메모리(
Sweep)를 순회하려면 각 블록의 크기를 알아야 하는데,Accessor를 모르면BlockHeader를 또 붙여야 해서 메모리가 낭비된다."
- "Pool은
- 최종 결정: Accessor as Header
- 캡슐화를 위해
BlockHeader를 추가하는 것은 메모리 낭비입니다. - Pool 구현부(
Pool.cpp)에서IAccessor.h를 포함하도록 하여,Accessor자체를 메모리 블록의 헤더로 활용합니다. - Pool은
Accessor의TypeInfo를 통해 블록 크기를 알아내고Compaction과Sweep을 수행합니다.
- 캡슐화를 위해
5. 배열 지원 API
- 논의:
MakePtr[4]같은 문법이 가능한가? - 결정: 비표준 문법 대신 C++ 표준(
std::make_shared)과 동일한MakePtr<T[]>(count)방식을 채택했습니다. 이를 위해Accessor<T[]>부분 특수화를 구현하여 배열 개수 관리 및 소멸자 역순 호출을 지원하기로 했습니다.
'개인 프로젝트 > Memory Project' 카테고리의 다른 글
| [ Memory Project ] 개발 일지 - 25/11/17 (0) | 2025.11.17 |
|---|---|
| [ Memory Project ] 개발 일지 - 25/11/12 (0) | 2025.11.12 |
| Memory 관리 방법 (3) | 2024.01.15 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 모던 C++ 챌린지
- 포인터
- logproject
- FloatingPoint
- cmakelists
- 그래픽스
- void* pointer
- 내부단편화
- void pointer
- MemoryProject
- 개발 일지
- RHICommand
- 세그먼테이션기법
- 메모리
- SFINAE #Template #C++
- GC
- RHI
- std::is_base_of
- 보이드 포인터
- 뮤택스
- 외부단편화
- 로드리게스 회전 행렬
- Reflection
- pointer
- 페이징기법
- cmake
- 증명
- 수학
- GameEngine
- C++
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
글 보관함