
솔직히 고백하자면, 저도 한때는 '제대로 된' 도구에 집착하던 시절이 있었습니다. 성능 문제가 발생하면 일단 `perf`나 `VTune`, 혹은 `Callgrind` 같은 묵직한 프로파일러부터 설치하고 봐야 진정한 엔지니어라고 생각했죠. 누군가 디버거에서 실행 중인 프로그램을 강제로 멈춰서(Pause) 현재 호출 스택을 확인해 보라고 조언했을 때, 속으로 콧방귀를 뀌었던 기억이 납니다. "아니, 그건 너무 주먹구구식 아니야?"라고 생각했으니까요. 하지만 10년 넘게 척박한 개발 환경에서 구르다 보니, 이제는 인정하게 되었습니다. 때로는 가장 원시적인 방법이 가장 우아한 해결책이 될 수 있다는 사실을 말이죠. 오늘은 복잡한 설정 없이도 치명적인 성능 병목을 잡아내는, 이른바 'Ctrl-C 프로파일링'에 대해 이야기해 보려 합니다.
개발자라면 누구나 한 번쯤 겪어봤을 상황을 예로 들어보겠습니다. 디버그 빌드로 프로그램을 실행했는데 초기화에만 1분이 넘게 걸리는 겁니다. 팀원들은 "어차피 디버그 모드니까 느린 게 당연해"라며 대수롭지 않게 넘기곤 하죠. 하지만 매번 빌드하고 테스트할 때마다 1분을 멍하니 기다리는 건 엄청난 리소스 낭비입니다. 참다못해 저는 프로그램을 실행하고, 아무 생각 없이 `Ctrl-C`를 눌러 프로세스를 중단시켰습니다. 그러자 GDB가 뱉어낸 호출 스택(Stack Trace)에는 놀랍게도 `nlohmann::json` 파서 관련 함수들이 끝도 없이 찍혀 있었습니다. 릴리스 빌드에서는 인라인(Inline) 최적화로 사라졌을 코드가, 디버그 빌드에서는 수십억 개의 함수 호출을 만들어내며 CPU를 잡아먹고 있었던 것이죠. 거창한 도구 없이 그저 실행을 멈춰본 것만으로 범인을 찾았고, 곧바로 수정할 수 있었습니다.
더 황당하면서도 재미있는 경험도 있습니다. MIPS 아키텍처 환경에서 링크 속도를 높이기 위해 `gold` 링커 대신 더 빠르다는 `LLD`로 교체했을 때의 일입니다. 이론상 빨라져야 하는데, 이상하게도 코어 덤프(Core Dump)를 열 때마다 GDB가 먹통이 될 정도로 느려졌습니다. 도대체 GDB가 혼자서 뭘 하고 있는지 궁금해서, 실행 중인 GDB 프로세스에 또 다른 GDB를 붙여(Attach) `Ctrl-C`를 눌러봤습니다. 스택을 확인해 보니 `dwarf_decode_macro_bytes`라는 함수에서 헤매고 있더군요. 알고 보니 GCC 컴파일러가 생성한 DWARF 매크로 정보를 LLD 링커가 GNU 링커처럼 처리하지 못했고, GDB는 이 꼬인 정보를 해석하느라 진을 빼고 있었던 겁니다. 결국 `-ggdb3` 옵션을 제거하는 아주 간단한 조치로 문제를 해결했습니다.
이 방법이 통하는 논리는 아주 단순합니다. 이를 '빈도가 매우 낮은 샘플링 프로파일러(Sampling Profiler)'라고 볼 수 있습니다. 만약 여러분의 프로그램이 바보 같은 실수(예: 무한 루프, 비효율적인 파싱, 락 경합)로 인해 느려졌다면, 프로그램은 실행 시간의 대부분을 그 문제의 코드 구간에서 보내고 있을 겁니다. 그렇다면 아무 때나 무작위로 `Ctrl-C`를 눌러 멈췄을 때, 여러분이 보게 될 화면은 바로 그 병목 지점일 확률이 압도적으로 높습니다. 복잡한 `perf` 설정을 하거나, 프레임 포인터(Frame Pointer) 옵션을 켜고 다시 빌드할 필요도 없습니다. 그저 디버거를 붙이고, 멈추고, 스택을 들여다보는 것만으로 충분합니다.
물론 이 방법이 만능은 아닙니다. 만약 프로그램이 전반적으로 2~3% 정도 느려졌거나, CPU 분기 예측(Branch Prediction) 실패 같은 마이크로 아키텍처 레벨의 최적화가 필요하다면 `Ctrl-C`로는 원인을 찾기 어렵습니다. 예를 들어 MIPS나 RISC-V에서 컴파일러가 함수 주소를 레지스터에 로드하는 방식과 링크 타임 최적화(Relaxation) 사이의 미묘한 차이로 발생하는 성능 저하는 시뮬레이터 기반의 정밀한 프로파일러가 아니면 잡아낼 수 없죠. 이런 미세한 성능 회귀(Regression)를 잡으려면 여전히 정교한 도구와 데이터가 필요합니다.
하지만 우리가 현업에서 마주치는 대부분의 '답답한' 상황들은 생각보다 단순한 원인에서 비롯됩니다. 레거시 코드의 늪에 빠져 있거나, 프로파일러조차 돌리기 힘든 임베디드 장비나 보안 환경에서 작업하고 있다면, 주저하지 말고 `Ctrl-C`를 활용해 보세요. 굳이 멋진 도구를 쓰지 않아도 문제를 해결했다면 그게 바로 실력입니다. 때로는 모니터 앞에서 팔짱을 끼고 도구 사용법을 검색하는 것보다, 과감하게 프로세스의 숨통을 끊어보는 것이 정답으로 가는 지름길일 수 있습니다. 오늘 커피 한 잔 마시고 돌아가서, 유난히 느린 그 프로세스에 디버거를 붙여보는 건 어떨까요?


