Rust의 derive는 조용히 #[inline]을 붙인다, uv는 이걸 막아 바이너리를 160KB 줄였다
- Rust는
#[derive(Debug)]같은 파생 매크로를 확장할 때#[inline]을 함께 붙임. 참조 문서에 예시로 암시돼 있을 뿐 보장된 동작은 아님. - uv 팀은 Debug 구현의 인라인을 막아 바이너리 크기를 약 160KB 줄임. derive(Debug)처럼 동작하되
#[inline(never)]를 쓰는 자체 proc macro를 만들어 적용함. - 깊게 중첩된 에러 enum의 Debug 구현은 인라인되면서 코드가 크게 불어나는데, rustc는 인라인되는 코드의 크기나 횟수에 제한을 두지 않는 것으로 보임.
- rust-lang/rust PR 117727에서
#[inline]을 추가한 것은 벤치마크에서 컴파일 시간과 바이너리 크기를 줄였고, 필드 5개 초과 구조체에 인라인을 생략하는 실험(PR 118031)은 오히려 성능 회귀를 냄.
Hacker News 의견들
Debug는 절대 인라인하면 안 된다고 봄. Display도 마찬가지고, fmt 기계가 워낙 무거워서 시리얼라이제이션 많은 작업에서도 인라인 안 한다고 병목이 되진 않더라. 내 Debug/Display 구현에 #[inline(never)] 붙여서 바이너리가 수십 KB 줄었음.
그 주석 넣은 PR(117727) 보면 벤치마크에서 컴파일 시간이랑 바이너리 크기 둘 다 줄었다고 함. 필드 5개 넘는 구조체엔 inline 안 붙이는 실험(118031)은 오히려 회귀 났고. Rust 개발자도 lobste.rs에서 버그 리포트 쌓이면 다시 조정할 생각 있다고 했으니 글쓴이가 낼 리포트에 얹는 것도 좋겠음.
그냥 PGO 쓰면 이런 인라인 추측을 다 피할 수 있지 않나. 실제 런타임 프로파일 보고 인라인을 넣고 빼니까.
uv는 이미 PGO 쓰고 있음. PGO가 '맞는 것만' 인라인하도록 보장할 일반적인 방법은 없는 듯.
Debug는 아예 처음 쓸 때 지연 생성하면 좋겠음. 99%는 안 쓰이는데 전부 확장되고, 나머지도 인라인으로 표시하는 건 명백히 틀렸지. 실제 구현은 엄청 어렵겠지만. 근거로 든 그 성능 PR도 개선과 회귀가 섞여 있었고.
LLVM 인라인 휴리스틱이 사실상 return true라는 농담도 있음. opt-level s나 z, 또는 #[inline(never)]로 조절할 수 있는데 인라인이 너무 적으면 성능이 크게 나빠지니 프로파일 없이 맞추기 어려움.
facet처럼 테이블 기반 derive(Debug)는 생각 안 했나? 이렇게 정형화된 코드에서 실행 속도보다 바이너리 크기랑 컴파일 시간이 중요하면 그게 먼저 떠오르는데, facet도 그 부분에서 약속을 못 지킨 걸 보면 std에서도 시도했다가 접었을 수도.