프로파일러는 그만, 컴파일러가 막아준다: OxCaml의 '무할당' 검사 @zero_alloc

한눈에 보기
Jane Street가 만든 OCaml 상위 집합(superset) 언어 OxCaml에는 흥미로운 기능이 하나 있습니다. 바로 함수에 [@zero_alloc] 어노테이션을 붙이면, 그 함수와 그 아래 호출 트리 전체에서 힙 할당(heap allocation)이 일어나지 않는다는 것을 컴파일러가 직접 검증해 준다는 점입니다.
할당이 단 한 군데라도 발생하면? 빌드가 그냥 실패합니다. 그리고 컴파일러가 어디서 할당이 생겼는지 알려주죠.
왜 이게 중요할까
성능이 중요한 코드, 특히 수백만 번 반복되는 **핫 패스(hot path)**에서는 불필요한 힙 할당이 성능을 갉아먹습니다. 그래서 보통 이렇게 일합니다.
- C, C++, Java, Go, C#, Rust, Zig, OCaml 등 대부분의 언어 → 프로파일러로 할당 지점을 찾아내 하나씩 제거
- 문제는, 핫 패스의 한 줄만 수정해도 다시 할당이 슬그머니 들어올 수 있다는 것
- 그러면 이전에 했던 최적화 맥락을 잊은 채, 또 프로파일러를 켜고 같은 조사를 반복하게 됩니다
즉 기존 방식은 "문제가 생긴 뒤에 사후적으로 찾는" 접근입니다. [@zero_alloc]은 이 순서를 뒤집습니다. 빌드 시점에 컴파일러가 회귀(regression)를 차단하기 때문에, 애초에 할당이 들어간 코드가 통과하지 못합니다.
관례(convention)와 컴파일러 검사의 차이
Zig나 최신 Rust 일부에서는 allocator를 함수 인자로 전달하는 패턴으로 비슷한 효과를 노립니다. allocator를 안 받는 함수는 할당을 못 하게 만들자는 아이디어죠.
하지만 토론에서 지적되듯, 이건 결국 관례일 뿐입니다. 함수 내부에서 얼마든지 새 allocator를 만들어낼 수 있으니까요. 관례는 무시될 수 있지만, 컴파일러 검사는 무시할 수 없습니다. 이것이 핵심 차이입니다.
다른 언어들의 비슷한 시도
커뮤니티(Lobste.rs) 반응을 보면 이미 유사한 개념이 존재했다는 의견도 있습니다.
- D 언어의
@nogc: 꽤 오래전부터 GC 할당을 금지하는 속성을 지원해 왔습니다 (한때@nogc예외 처리 실험도 있었음) - Rust의
core만 사용하기:alloc/std없이 작성하면 할당을 피할 수 있지만, 이는 "의존성 통제" 또는 "호출 그래프 수동 확인"에 가까운 접근입니다
다만 글의 초점은 단순히 "할당을 피하는 방법"이 아니라, 컴파일러라는 도구가 특정 속성을 정적으로 강제하고, 그 속성이 깨지는 지점을 생산적으로 짚어주는 작업 흐름에 있습니다.
더 나아가면?
한 댓글의 질문이 인상적입니다. 이런 컴파일러 강제 방식이 무할당뿐 아니라 다른 속성에도 적용될 수 있지 않을까요?
- "예외를 던지거나 패닉하지 않음"
- "잠금(lock)을 사용하지 않음"
- "항상 종료함(termination 보장)"
성능과 안정성에 직결되는 이런 보증들을 컴파일러가 빌드 단계에서 보장해 준다면, 우리가 코드를 신뢰하는 방식 자체가 달라질 수 있습니다. OxCaml의 [@zero_alloc]은 그 가능성을 보여주는 작은 출발점인 셈입니다.
정리
[@zero_alloc]은 함수 호출 트리 전체의 힙 할당 금지를 컴파일러에 선언하는 기능- 사후 프로파일링이나 팀 규칙이 아니라, 빌드 시점 강제라는 점이 가장 큰 차별점
- 더 많은 언어가 "관례"를 넘어 "컴파일러 검사"로 이런 보증을 제공할 필요가 있다는 메시지