24.4.1 병렬 빌드 스케일링으로 인한 시스템 메모리 포화(OOM) 유발 인과성
ROS2 빌드 시스템(Colcon)에서의 병렬 빌드로 인한 시스템 메모리 포화(OOM, Out of Memory) 발생 메커니즘을 규명하여 기술한다.
Colcon 빌드 시스템은 전체 컴파일 성능 최적화를 위해 기본적으로 시스템 가용 구조 상의 모든 CPU 코어를 활용해 다수의 패키지를 병행 컴파일한다. 이러한 형태의 병렬 빌드 스케일링(Parallel Build Scaling)은 소프트웨어 스택의 빌드와 릴리즈 시간은 감소시키지만, 자원이 제한적인 임베디드 환경(Embedded System Environment)이나 엣지 계층의 단일 보드 컴퓨터(SBC, Single Board Computer) 위에서는 물리적 한계를 넘어서는 메모리 고갈 문제를 야기할 수 있다.
1. 병렬 컴파일 작업 스케줄링과 자원 소모 모델 연관성
ROS2 C++ 소스 코드를 기계어로 번역하기 위한 과정에서 활용되는 GCC(GNU Compiler Collection) 혹은 Clang 기반 C++ 컴파일러 프로세스는 각 소스 파일(.cpp)을 단일 목적 파일(.o) 내의 컴파일 유닛(Translation Unit) 단위로 변환하는 데에 막대한 양의 물리적 메모리(Physical Memory)를 점유한다. 이는 소스 코드가 어휘 분석 및 구문 분석(Parsing) 과정을 거치며 형성되는 추상 구문 트리(AST, Abstract Syntax Tree) 메모리 로드 현상, 고강도 제네릭 템플릿 인스턴스화(Template Instantiation), 중간 표현(Intermediate Representation, IR) 생성, 그리고 기계어 대상 레지스터 할당 최적화 등의 알고리즘 실행 로드가 합쳐지기 때문이다.
이러한 특성상 컴파일러 프로세스의 단일 인스턴스가 소모하게 되는 메모리 요구량을 M_{compiler}라 하고, 동시 활성화된 병렬 작업 스레드의 수를 N_{jobs}라 할 때, 빌드 시스템 전체에서 소비하는 총 요구 시스템 메모리 M_{total}은 다음의 식으로 간략히 표현된다.
M_{total} \approx (N_{jobs} \times M_{compiler}) + M_{os}
여기서 M_{os}는 운영체제 커널 계층 시스템 데몬(System Daemon)이 보장받아야만 하는 최소한의 베이스라인 시스템 메모리를 의미한다. Colcon의 주 프레임워크(colcon-core 확장 모듈) 설계는 방향성 비순환 그래프(DAG, Directed Acyclic Graph)에 의한 패키지 컴파일 의존성 허용도가 유지되는 수준 내에서 시스템 물리 혹은 논리 코어 개수에 대응되는 N_{jobs}를 무제한적으로 병렬 스케줄링(Scheduling)함으로써 이 연산량을 고의적으로 한계까지 밀어붙인다.
2. 시스템 메모리 포화 임계점 도과 및 OOM-Killer 프로세싱
거대 단일 패키지 및 상호 의존성을 보유하지 않은 복수의 패키지에 대하여 병렬 작업이 다수 중첩될 때, 요구되는 메모리 자원 크기인 M_{total} 값이 물리적 RAM 용량 임계점(Saturation Threshold)을 초월하는 현상이 유발된다. 해당 임계점이 돌파될 시점으로 진입할 경우 리눅스 커널(Linux Kernel)의 코어 기능은 시스템 동작 불능 상태를 제어하기 위한 스케줄링 개입을 시도한다.
- 스와핑(Swapping) 단계 진입: 물리적 RAM 한계를 보완하기 위하여 리눅스는 가상 메모리 증편 장치인 스왑 영역(Swap Space) 파티션 영역으로 비활성 블록을 페이징 쓴다.
- 스래싱(Thrashing) 전개 단계: C++ 컴파일러 프로세스의 행위 패턴은 단일 시점에 방대한 심볼 테이블 트리 참조 및 외장 헤더 포함을 통한 임의 접근(Random Access) 양상을 시현하므로 높은 확률의 페이지 폴트(Page Fault)를 지속 초래하며 시스템 내부적으로 극진적인 I/O 병목 상태를 몰고 간다.
- OOM-Killer 개입 절차: 스왑 영역 자원 역시 고갈되고 I/O 대기율이 기형적으로 치솟을 때 커널 레벨 시스템 보호 장치인 OOM-Killer 스코어링이 휴리스틱에 따라 메모리 최우선 점유 개체(
c++,cc1plus데몬 루틴)를 색출한다. 그리고SIGKILL전송을 거쳐 데몬을 강제 파괴(Terminate) 시킴으로써 시스템 여유 버퍼를 강탈한다.
최종적으로 자식 프로세스가 타의에 의해 추방되면, 상위 Colcon 메타 빌드 프레임워크는 포크되었던 개별 프로세스의 비정상 종료 코드(Unix 시그널 관행 상 Exit Code 137로 도출됨)를 탐지하고 연달아 파이프라인 중단(Abort Pipeline) 명령으로 빌드 연쇄를 멈춘다.
3. 대단위 템플릿 메타 프로그래밍과 스파이크 메모리 오버헤드 특성
ROS2 프레임워크의 주력 프로그래밍 인터페이스 API 플랫폼 rclcpp는 C++14를 필두로 한 고도화된 정적 템플릿 메타프로그래밍(Template Meta-programming) 구조체에 기반하고 있다. 타입 체계의 엄격한 무결성을 제공하기 위하여 퍼블리셔(Publisher) 클래스의 제네릭, 서브스크립터(Subscriber) 콜백에 대한 다변수 템플릿(Variadic Templates), 이벤트 루프 익스큐터(Executor) 스핀 패턴에 이르기까지 제네릭 메타 코딩이 강제된다. 이는 런타임이 아닌 컴파일 타임(Compile-time) 중의 과다 연산을 이끌어 내며 각 번역 유닛 당 M_{compiler} 요구 수치를 증폭시키는 구조적 스파이크 오버헤드를 안게 된다. 이에 극도로 낮은 N_{jobs} (예: 2 혹은 4 스레딩) 할당 환경이라 할지라도 단일 SBC 디바이스 환경 내에서는 순간적인 임계점 돌파, 즉 OOM을 트리거해 버리는 인과 구조를 보유하고 있다.
출처
- ROS2 Colcon Meta Build System Internal Architecture Documentation (Version: Humble / Jazzy)
- GNU C Compiler Collection (GCC) Internals and Parser Memory Management Layout
- Linux Kernel Mailing List(LKML) Memory Management, Demand Paging, and OOM Killer Algorithms