콘텐츠로 이동

2026년 빠른 빌드 도구 체인: sccache, mold, nextest, 그리고 기다리는 비용

· 13 min read · default
developmentperformancerustbuild-toolsciproductivity

대기에 대한 잘 알려진 관찰이 있습니다: 10초 미만이면, 당신은 초점을 유지합니다; 1분 넘으면, 당신은 문맥을 전환하고 스레드를 잃습니다. 빌드 시간은 정확히 그 위험한 범위에 앉습니다. 90초 컴파일은 90초만 비용을 들지 않습니다 — 전환한 탭에서 돌아왔을 때 당신이 보유하고 있던 모든 것을 다시 로드하는 비용입니다. 하루 50개 빌드로 팀에 곱하고, 느린 빌드는 불편을 중단하고 행동을 형성하기 시작합니다: 반복이 비싸기 때문에 더 큰, 더 위험한 커밋, 피드백 루프가 처벌하기 때문에 적게 리팩토링, 그리고 너무 오래 걸리기 때문에 로컬로 테스트를 건너뜁니다.

2026의 빌드 성능에 대해 격려하는 것은 가장 큰 승리가 보통 구성이고, 엔지니어링이 아니라는 것입니다. 당신은 코드베이스를 재구성할 필요가 없습니다; 당신은 시간이 실제로 가는 세 가지 구별되는 단계를 처리하는 몇 가지 도구를 설치합니다. 이 가이드는 그 단계들을 다룹니다 — 컴파일, 링킹, 테스트 — sccache, mold, cargo-nextest를 통해, 더하기 내부 루프를 위한 bacon, 그리고 중요하게, 어떤 것이 실제로 도움이 되었는지 측정하는 방법.

최적화 전 측정

가장 일반적인 실수는 잘못된 단계를 최적화하는 것입니다. "빌드는 느리다"는 진단이 아닙니다 — 빌드는 최소한 컴파일, 링킹, (당신이 그것을 실행하면) 테스트이며, 그 사이의 균형은 프로젝트에 따라 엄청나게 다릅니다. 많은 작은 크레이트와 거대한 최종 바이너리를 가진 코드베이스는 대부분의 시간을 링킹에 쓸 수 있습니다; 무거운 제네릭과 매크로를 가진 코드베이스는 컴파일에 의해 지배될 수 있습니다; 성숙 프로젝트는 둘 중 하나에서 테스트에서 더 많은 시간을 쓸 수 있습니다.

그래서 추측 대신 타이밍 분류로 시작하세요. Rust에서, cargo build --timings는 정확히 각 크레이트가 얼마나 오래 걸렸는지 그리고 병렬성이 어디에서 고착했는지를 보여주는 HTML 보고서를 생성합니다 — 자주 한 종속성이 다른 모든 것을 직렬화한다는 것을 드러냅니다. 더 거친 보기의 경우, 깨끗한 나무 vs 증분에서 time cargo build는 얼마나 많이 당신이 콜드 빌드를 지불하는지를 말해줍니다. 그리고 hyperfine은 단일 노이즈 있는 실행보다 통계적으로 건전한 전/후 비교를 제공합니다.

이것이 중요한 이유는 아래의 각 도구가 하나의 단계를 처리하고 다른 곳에서는 아무것도 하지 않기 때문입니다. 당신의 병목이 컴파일일 때 더 빠른 링커를 설치하면 반올림 오류와 거짓 진행감이 나타납니다.

컴파일: 이미 빌드한 것을 재구성하는 것을 중단하세요

컴파일에서의 가장 일반적인 낭비의 원천은 중복입니다 — 동일한 플래그를 가진 동일한 종속성 크레이트, 당신이나 동료나 CI 러너가 한 시간 전에 이미 구축한 것을 구축합니다. sccache는 이것을 직접 처리합니다: 컴파일러를 래핑하고, 입력을 해싱하고, 정확히 그 컴파일을 이미 본 적이 있으면 캐시된 아티팩트를 반환합니다. Rust, C/C++, CUDA를 지원합니다.

sccache를 로컬 캐시를 넘어 승격시키는 것은 공유 저장소 백엔드입니다 — S3, GCS, Redis, 또는 GitHub Actions 캐시. 그것은 특히 CI에서 경제를 변경합니다. 기본 CI 경험은 모든 실행에서 처음부터 모든 종속성을 컴파일하는 콜드 머신인데, 순수 낭비입니다 그 종속성이 변경되지 않았기 때문입니다. 공유 캐시를 사용하면, 첫 실행이 그것을 채우고 이후 모든 실행이 다운로드 대신 컴파일합니다. 팀은 보통 CI 빌드 시간이 절반 이상 떨어지는 것을 봅니다, 그리고 동일한 캐시는 개발자 머신을 제공합니다.

설치는 단일 환경 변수 (RUSTC_WRAPPER=sccache) 또는 ~/.cargo/config.toml 항목입니다. 중요한 후속은 검증입니다: sccache --show-stats는 당신의 히트 레이트를 보고하고, 낮은 히트 레이트는 당신의 입력에서 뭔가 다양하고 있다는 신호입니다 — 불안정한 RUSTFLAGS, 출력에 구워진 절대 경로, 또는 증분 컴파일 간섭. 나쁜 히트 레이트가 있는 캐시는 없는 것보다 나쁩니다, 당신이 아무것도 위해 조회 비용을 지불하기 때문입니다.

링킹: 직렬화된 꼬리

링킹은 사람들이 잊어버리는 단계이고 자주 최악의 범죄자입니다. 이유는 다음입니다: 컴파일이 아름답게 코어에 걸쳐 병렬화하지만, 링킹은 전통적으로 하지 않습니다. 16개 코어에서 200개 파일을 20초에 컴파일하고 한 코어가 링크하는 8초를 기다립니다. 증분 재구축에서 — 한 파일을 변경하고 단지 그 파일을 재컴파일하는 — 링크는 당신의 대기의 대부분일 수 있습니다.

mold는 시작부터 모든 사용 가능한 코어를 사용하도록 설계된 드롭인 링커입니다. 그것은 자주 링크 시간을 한 자릿수로 줄이고, 증분 재구축 경로에서의 승리가 랜딩되기 때문에, 개발자들이 가장 즉시 느끼는 변경입니다. 채택은 진정 사소합니다: mold -run cargo build는 구성 없이 모든 빌드 명령을 래핑하고, 또는 영구 설치를 위해 링커 플래그에 -fuse-ld=mold를 추가합니다.

mold를 sccache와 결합할 이유는 그들이 동일한 대기의 다른 절반을 공격한다는 것입니다. sccache는 당신이 이미 한 컴파일을 제거합니다; mold는 캐시될 수 없는 남은 링크를 가속화합니다. 둘 중 어느 것도 다른 하나를 대체하지 않고, 함께 그들은 보통 어느 하나보다 더 큰 개선을 생성합니다.

테스트: 격리 및 정직한 실패

세 번째 단계는 테스트이고, 여기서 cargo-nextest는 단지 속도보다 모델을 변경합니다. 표준 cargo test는 하나의 프로세스 내에서 바이너리의 모든 테스트를 실행합니다; nextest는 자신의 프로세스에서 각 테스트를 실행합니다. 그것이 전형적인 2–3배 속도 향상을 넘어 몇 가지 결과를 생성합니다.

격리가 실제가 됩니다. 테스트는 서로의 글로벌 상태를 손상시킬 수 없으므로, 순서 종속 실패가 즉시 표면화되는 대신 몇 개월 후 신비롭게 나타납니다. 충돌이 기여할 수 있습니다 — 테스트가 세그먼트하거나 중단되면, 당신은 어느 것이 배웠는지, 전체 바이너리의 결과를 잃는 대신. 불안정한 테스트가 그렇게 명명되어 있습니다: --retries로, 실패하고 전달하는 테스트는 조용히 전달 시에 재실행되는 대신 FLAKY로 보고되고, 불안정한 테스트는 실패한 것과 다른 문제이고 숨기는 것은 스위트가 어떻게 썩는지입니다. 그리고 CI 샤딩이 기본 제공입니다 via --partition, 따라서 실행자에 걸쳐 스위트를 분할하는 것은 스크립팅 프로젝트가 아닌 플래그입니다.

알 주요 격차: nextest는 doctests를 실행하지 않으므로, 공통 CI 패턴은 cargo nextest run && cargo test --doc입니다.

내부 루프: 빌드를 수동으로 실행하지 마세요

가장 빠른 빌드는 당신이 호출할 필요가 없는 것입니다. bacon은 사이드 터미널에서 실행하고, 당신의 소스를 보고, 모든 저장에서 cargo check, clippy 또는 테스트를 다시 실행하고, 컴팩트한 항상 현재의 오류 요약을 보여줍니다. 이득은 생 속도가 아닙니다 — 그것은 컴파일이 당신의 생각과 겹치는 대신 그것을 차단하고, 당신이 스크롤 출력 대신에 두드러지게 첫 오류를 봅니다.

이것이 자연스럽게 단계 도구와 쌍을 이루는 이유입니다: bacon이 지속적 피드백을 제공하고, sccache와 mold가 각각의 배경 실행을 빠르게 충분히 완성하여 당신이 이전 오류를 읽는 것을 끝냈을 때. 비Rust 프로젝트의 경우, watchexec은 모든 명령에 대한 동일한 지속적 피드백 패턴을 제공합니다.

그것을 함께 두고, 검증

완전한 설치는 짧습니다:

cargo install sccache cargo-nextest bacon --locked
sudo apt install mold        # or brew install mold
# ~/.cargo/config.toml
[build]
rustc-wrapper = "sccache"

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

그 다음 각 부분을 독립적으로 검증하세요, 조용한 misconfiguration이 쉽기 때문에: sccache --show-stats가 오르는 히트 레이트를 보여줘야 합니다; readelf -p .comment ./target/debug/yourbin | grep -i mold는 mold가 실제로 그것을 연결했는지를 확인합니다; cargo nextest runcargo test보다 명확하게 더 빨리 끝나야 합니다. 클린 빌드 대신 현실적인 변경 — 하나의 파일을 터치, 재구축 — 에 대해 hyperfine으로 종단 간 결과를 측정합니다, 정확히 당신이 모든 날 무엇을 수행하기 때문입니다.

CI에서, 공유 캐시 백엔드 추가합니다 (GitHub Actions에서 SCCACHE_GHA_ENABLED=true, 또는 S3 버킷) 그리고 nextest의 --partition을 사용하여 실행자를 가로질러 샤드합니다. CI는 CI 머신이 기본적으로 콜드이고 모든 실행에서 동일한 낭비 일을 반복하기 때문에 이 도구들이 가장 극적으로 지불하는 곳입니다.

구성을 넘어: 실제로 빌드를 느리게 만드는 것

단계 도구를 적용했고 루프가 여전히 고통스럽다면, 남은 원인은 구조적입니다 — 그리고 행동하지 않기로 결정하더라도 이해하기 가치가 있습니다, 왜냐하면 그들이 일부 프로젝트가 최적화에 저항하는 이유를 설명하기 때문입니다.

종속성 수와 깊이는 가장 일반적입니다. 당신이 의존하는 모든 크레이트는 최소한 한 번 컴파일되어야 하고, 깊은 종속성 그래프가 직렬화합니다: 크레이트 C는 B가 끝날 때까지 시작할 수 없고, 그것이 A를 기다렸습니다. cargo build --timings는 유휴 코어가 있는 긴 중요 경로로 이것을 직접 보여줍니다. 당신이 단지 하나의 보조 함수를 위한 큰 크레이트를 당기는 경우 — 종종 가장 최고 레버리지 구조 수정 — 감사 종속성을 자주 이는 뿐만 아니라 공급망 표면도 줄입니다.

제네릭 및 매크로 무거운 코드는 인스턴시에이션에 비례하여 컴파일 시간을 비용합니다. 20개 타입으로 사용되는 제네릭 함수는 20번 컴파일되고, 무거운 절차 매크로는 컴파일 시간에 임의의 코드를 실행합니다. 뜨거운 제네릭이 제네릭일 필요가 없는 곳에서, 그것을 수동으로 일원화하거나 그것의 경계를 좁히는 것은 측정 가능하게 컴파일 시간을 줄일 수 있습니다. 이것은 인체공학에 대한 실제 상충이므로, 아래로 윤곽을 그리기 전에 측정합니다.

크레이트 세분화는 양쪽을 자릅니다. 하나의 거대한 크레이트는 내부적으로 병렬화할 수 없고 작은 편집을 위해 전체 재컴파일을 강제합니다; 수백 개의 작은 크레이트는 크레이트별 오버헤드를 추가하고 더 깊은 종속성 체인을 강제합니다. 유용한 휴리스틱은 다른 속도로 변경되는 경계를 따라 분할하는 것입니다 — 안정적 기초 코드를 자신의 크레이트에 두어서 변수 코드에 대한 편집이 그것을 재구축하지 않습니다.

디버그 정보 및 최적화 설정이 가장 저렴한 구조 레버입니다. 전체 디버그 정보는 생성하고 연결하기에 비싸고; debug = 1 (라인 테이블만)은 대부분의 비용의 분수에서 백트레이스에 충분합니다. 그리고 당신이 절대 단계하지 않을 종속성을 위해, opt-level 오버라이드는 모든 것을 최적화하는 비용을 지불하지 않고 당신의 자신의 코드를 최적화합니다.

이들 중 어느 것도 구성 변경이 아닙니다, 도구 후에 그들이 속하는 이유입니다. 하지만 프로젝트가 캐싱 및 빠른 링커에도 불구하고 느린 상태로 유지되면, 답은 거의 항상 이 목록에 있습니다.

언제 중단할지 아는 것

마감 주의: 빌드 최적화 자체는 수확 체감이 있는 작업이고, 생산적인 느낌에 비정상적으로 좋습니다. 한 번 당신의 증분 재구축이 몇 초인 경우, 추가 조정은 약간을 구매하고, 남은 레버는 점진적으로 더 침입적입니다 — 크레이트 경계 재구조화, 종속성 절단, 제네릭 재작업. 그들은 행동할 가치가 있지만, 그들은 구성 변경이 아닌 엔지니어링 프로젝트입니다.

정직한 순서입니다: 먼저 측정하고, 저렴한 단계별 수정을 적용 (캐시, 링커, 테스트 러너), 다시 측정하고, 루프가 더 이상 당신의 집중을 끊지 않을 때 중단합니다. 목표는 절대 벤치마크 숫자가 아니었습니다 — 완성하던 생각을 저장했을 때 당신이 충분히 오래 흐름에 머물기 위한 것이었습니다.

결론

느린 빌드는 일정을 변경하고, 행동을 변경합니다. 그리고 2026의 가장 큰 수정은 엔지니어링이 아니라 구성입니다. 실제로 비용이 드는 단계를 진단하세요 — cargo build --timingshyperfine은 직관을 이기고 — 그 다음 그것을 처리하는 도구를 적용합니다: sccache는 당신이나 CI가 이미 구축한 것을 재컴파일하는 것을 중단하고, mold는 증분 재구축을 지배하는 직렬화 링크를 병렬화하고, cargo-nextest는 더 빠르고, 고립되고, 불안정 인식 테스트이고 내장 CI 샤딩이고, bacon은 대기하는 동안이 아니라 생각하는 동안 컴파일이 일어납니다. 각각을 정말로 관여하는지 검증하고, 낭비가 가장 큰 곳인 CI에서 공유 캐시를 적용하고, 루프가 당신을 중단시키기를 중단했을 때 최적화를 중단합니다.

참고 및 리소스

도구

배경 및 분석

관련 1337skills 치트시트