[IT 심층 분석]
HDR 연산 확장을 위한 멀티 프레임 버퍼링 아키텍처 재설계와 메모리 파편화 해결
김민준 · IT 시스템 엔지니어|
현대의 모바일 카메라는 기본적으로 노출이 다른 여러 장의 사진을 찰나의 순간에 캡처하여 하나의 완벽한 이미지로 합성해내는 컴퓨테이셔널 포토그래피 즉 HDR(High Dynamic Range) 알고리즘에 전적으로 의존하고 있습니다. 최근 저희 시스템 랩에서는 차세대 센서 해상도가 5천만 화소 급으로 껑충 뛰면서 기존의 HDR 합성 엔진이 심각한 타임아웃 오류를 내뿜는 사태에 당면했습니다. 노출이 짧은 프레임과 긴 프레임 그리고 중간 프레임 최소 3장에서 많게는 7장까지의 원본 RAW 데이터를 램에 한꺼번에 올리고 픽셀 단위로 정렬(Alignment) 및 병합(Merging)을 수행해야 하는데 이전 세대의 메모리 할당 구조로는 이 거대한 페이로드를 단 몇 초 안에 안정적으로 담아낼 재간이 없었던 것입니다. 프로세스가 가용 메모리를 찾아 허덕이며 시스템 콜을 남발하다가 결국 강제 캐시 축출이 발생하고 사용자 앱이 응답 불능에 빠지는 일이 부지기수였습니다.
근본적인 문제는 멀티 프레임 환경에 전혀 대비되지 않은 낡은 버퍼 풀링의 부재였습니다. 각각의 5천만 화소 이미지는 10비트에서 12비트 심도로 기록될 때 장당 100 메가바이트 안팎의 덩치를 자랑합니다. 5장이 연속적으로 들어오면 순식간에 500 메가바이트의 물리적 공간이 요구되는데 안드로이드의 가상 머신 힙 메모리나 일반적인 커널 스페이스의 파편화된 빈 공간으로는 연속적인 주소 할당을 도저히 맞춰줄 수 없었습니다. 더욱이 ISP 레벨에서는 DMA 접근을 위해 반드시 인접 물리 주소를 요구하는 경우가 많아 이 파편화 문제는 시스템 패닉 수준의 병목을 초래했습니다. 이를 진단고자 KVM 메모리 디버거를 연결하여 추적한 결과 조각난 페이지들을 강제로 묶으려는 페이지 마이그레이션(Page Migration) 커널 스레드의 CPU 점유율 에러 로그가 산더미처럼 쌓여 있는 것을 확인했습니다.
해결의 단초는 부팅 과정에서부터 OS 영역과 완벽히 격리된 독립적인 하이퍼바이저 수준의 컨티규어스 메모리 영역(CMA - Contiguous Memory Allocator)을 초대형 사이즈로 떼어버리는 강수에서 비롯되었습니다. 디바이스 트리의 메모리 노드를 대대적으로 수정하여 카메라 서브시스템 전용으로 기가바이트 급의 영구 예비 공간을 선언한 뒤 이를 여러 개의 고정 사이즈 링 버퍼 슬롯으로 쪼개어 애플리케이션 프레임워크가 마치 공장의 컨베이어 벨트처럼 순환 참조하도록 아키텍처를 뒤엎었습니다. 단일 HDR 촬영 세션이 시작되면 시스템은 새로운 메모리를 할당받는 대신 이미 확보된 5개의 청크 슬롯 번호표만을 빠르게 취득하고 센서의 인터럽트 루틴으로 포인터 번호만 던져주었습니다.
이러한 사전 할당 방식에 더하여 합성 연산 과정 자체도 메모리 인-플레이스(In-place) 조작 기법으로 개조했습니다. 과거에는 합성 중간 결과물을 담기 위해 계속 임시 버퍼를 찍어냈으나 개조된 알고리즘은 기준이 되는 앵커 프레임 버퍼 위에 델타 변화값만을 직접 덮어쓰거나 수정하는 매우 거친 C++ 네이티브 포인터 산술 연산을 도입했습니다. 타켓 포인터 주소가 갱신될 때마다 파이프라인의 캐시 라인 미스를 최소화하기 위해 데이터 패딩을 64바이트 경계에 맞추어 강제 정렬하는 치밀함도 빼놓지 않았습니다. 길고도 험난한 이 커널 단비와 로우 레벨 메모리 개편이 마무리되자 5천만 고화소 7장 연사 HDR 처리 시간은 기존 8초대에서 1초 미만으로 극적으로 곤두박질쳤고 가비지 컬렉터의 불필요한 개입 또한 원천적으로 봉쇄되었습니다. 거대한 데이터의 홍수 속에서 엔지니어가 메모리를 움켜쥐고 통제하지 못한다면 알고리즘의 우수성은 결코 빛을 발할 수 없다는 것을 체감한 강렬한 프로젝트였습니다.