Zig: 빌드 시스템 재작업
(ziglang.org)- zig build 내부가 configurer와 maker 프로세스로 나뉘고,
build.zig가 만든 빌드 그래프는 바이너리 설정 파일로 직렬화됨 - 부모 프로세스는 설정 파일을 캐시하고 maker를 Release 모드로 비동기 컴파일하며, maker는 Zig 버전마다 전역 캐시에 한 번만 컴파일됨
- 사용자
build.zig변경 시 더 이상 빌드 시스템 전체를 함께 컴파일하지 않아--watch,--fuzz,--webui같은 기능 추가의 시간 부담을 줄임 zig build --help평균 실행 시간이 150ms에서 14.3ms로 줄었고, CPU 사이클·명령어 수·캐시 참조도 94~96%대 감소함- 대부분 API는 호환되지만
b.args직접 관찰은addPassthruArgs()로 대체되며, Zig0.17.0은 몇 주 안에 예정됨
빌드 시스템 구조 변경
- 대형 브랜치가 병합되며
zig build내부가 configurer 프로세스와 maker 프로세스로 분리됨 - 기존 구조에서는
build.zig파일과 빌드 시스템 구현 전체가 하나의 큰 Debug 모드 프로세스로 컴파일됐고,build.zig가 메모리에 빌드 그래프를 만든 뒤 “build runner”가 이를 실행했음 - 새 구조에서는
build.zig가 작은 Debug 모드 configurer 프로세스로 컴파일되고, 빌드 그래프는 바이너리 설정 파일로 직렬화됨 - 부모
zig build프로세스는 설정 파일을 감지해 다음 실행을 위해 캐시하고, 빌드 그래프 실행을 맡는 maker를 Release 모드로 비동기 컴파일함 - 설정 파일과 maker 컴파일이 준비되면 maker가 설정 파일을 받아 실행하며, maker는 전역 캐시 덕분에
zig version마다 한 번만 컴파일하면 됨
속도 개선 효과
- 핵심 목표는
zig build속도 개선이며, 사용자build.zig변경 시 더 이상 빌드 시스템 전체를 함께 컴파일하지 않음 --watch,--fuzz,--webui가 도입되면서 빌드 시스템 기능이 늘어나도zig build시간이 함께 늘지 않도록 하는 의미가 커짐- 변경이 없다고 판단되면
build.zig로직을 다시 실행하지 않고 이전 설정을 재사용할 수 있음 - 예를 들어
zig build명령줄에-freference-trace를 추가해도build.zig로직을 불필요하게 다시 실행하지 않음 - 실제 빌드 그래프를 실행하는 프로세스가 최적화 활성화 상태로 컴파일되므로 실행 단계도 빨라짐
벤치마크 결과
zig build --help평균 실행 시간은 이전 구조의 150ms에서 새 구조의 14.3ms로 줄어듦- 벽시계 시간은
150ms에서14.3ms로 90.4% 감소했고, CPU 사이클은593M에서24.1M으로 95.9% 감소함 - 명령어 수는
995M에서43.7M으로 95.6% 감소했고, 캐시 참조는25.8M에서1.46M으로 94.3% 감소함 - 피크 RSS는
84.8MB에서78.5MB로 7.4% 감소함 - 가장 큰 차이는 모든
zig build명령마다build.zig로직을 실행하던 방식에서, 캐시된 직렬화 설정을 재사용하는 방식으로 바뀐 데서 발생함
도구와 호환성 영향
- ZLS 같은 서드파티 도구는 빌드 러너를 포크해 유지하는 대신 직렬화된 설정 파일을 소비하는 방식으로 이득을 볼 수 있음
- 내부 메커니즘은 크게 바뀌었지만, API 관점에서는 대부분 호환 유지됨
- 예외 사항은 병합된 PR에 정리된 변경 사항에 해당함
주요 파괴적 변경
- 많은 사용자가 마주칠 가능성이 큰 변경은
b.args를 직접 관찰하던 패턴의 제거임 - 기존 코드:
if (b.args) |args| {
run_cmd.addArgs(args);
}
- 새 코드:
run_cmd.addPassthruArgs();
- 이 변경으로 빌드 스크립트는 해당 인자를 더 이상 관찰할 수 없게 되어 기능 하나가 제거됨
- 대신 해당 인자를 바꿔도 빌드 스크립트를 소스에서 다시 빌드할 필요가 없어짐
테스트와 릴리스 일정
- Zig 방향에 영향을 주고 싶은 사용자는 프로젝트를 개발 버전으로 올려 새 변경을 시험하고 피드백을 줄 수 있음
- Zig
0.17.0은 몇 주 안에 릴리스될 예정임 - 시간이 없어 미리 테스트하지 못했고
0.17.0에서 빌드가 깨지더라도,0.17.1태그에 수정이 들어갈 기회가 충분히 있음
댓글과 토론
Hacker News 의견들
-
Zig 0.16.0으로 일부 코드를 올려봤는데 결과가 꽤 만족스러웠음
영향받은 부분은 상당히 많았지만, 변경 자체는 좋았고 언어의 미래를 밝게 만드는 방향으로 보임
특히 새 입출력 메커니즘은 단일 스레드, 다중 스레드, 이벤트 루프 구현 모두에서 보기 좋은 형태로 효율적인 코드를 가능하게 함
0.16.0 이후 Zig를 아직 안 써봤다면 꼭 살펴보길 권함. 이번 릴리스 노트가 엄청 길었음
https://ziglang.org/download/0.16.0/release-notes.html- 아직 “매우 효율적”이라고 보긴 어려움. Io는 여전히 동적 디스패치이고 여러 단계의 간접 참조가 있음
알기로는 이전보다 느린 편임
다음 릴리스들에서 “디스패치 대상은 컴파일 타임에 알려져 있는데 여전히 동적”인 문제를 해결해서 효율 손실을 줄일 것으로 기대됨 - 새 입출력 메커니즘, 특히 async/await가 어떻게 동작하는지 이해하기가 좀 어려웠음
io.async를 이벤트 루프 기반 IO 구현에서 호출하면 내부적으로 “태스크” 같은 것을 시작하고, future는 그 핸들일 거라고 가정함
이해 안 되는 부분은future.await(io)를 호출할 때임. IO 구현이 현재 함수를 어떻게든 중단했다가 future가 해결되면 재개하는 건가? 그렇다면 Zig의 모든 함수가 스택 없는 코루틴이라는 뜻인가? - 언젠가는 이런 새 기능을 쓸 수 있겠지만, 안정성과 덜 테스트된 타깃 때문에 Zig 빌드에서
.use_llvm = true를 쓰게 됨
- 아직 “매우 효율적”이라고 보긴 어려움. Io는 여전히 동적 디스패치이고 여러 단계의 간접 참조가 있음
-
Zig를 몇 달 써보니 훌륭한 도구용 언어라고 확신하게 됨
아이디어를 자유롭게 대충 만들어볼 때 집어 들기 좋고, 막히는 지점마다 만든 사람들이 이미 고민해둔 편안한 해법이 있음
그렇다고 “올바른” 언어 사용법을 강요하진 않음
이제 내게는 “차고에서 뚝딱거리는” 기본 언어가 됨- “올바른” 사용법을 강요하지 않는다고 하기엔, 사용하지 않는 변수를 허용하지 않고 여러 줄 주석도 없음
내게는 꽤 큰 생산성 문제임 - 정말 그렇게 좋음?
내 “차고에서 뚝딱거리는” 기본 언어는 Python임. 가벼운 문법, 방해하지 않는 사용감, 기본 라이브러리 풍부함, 없는 건 패키지가 다 있음
Zig의 장점은 뭐임? - 이 부분에서 내 주된 걸림돌은, 나한테는 Mojo가 실험용 기본 언어가 될 것 같다는 점임
- 확실히 훌륭한 실험용 언어이긴 한데, Zig 팀과 커뮤니티는 언어를 “올바르게” 쓰는 방식에 대해 매우 강한 취향을 갖고 있음
- “올바른” 사용법을 강요하지 않는다고 하기엔, 사용하지 않는 변수를 허용하지 않고 여러 줄 주석도 없음
-
Andrew Kelley 인터뷰 영상을 보고 Zig를 배워보고 싶어졌음: https://www.youtube.com/watch?v=iqddnwKF8HQ
- Andrew는 정말 존중하고 Zig도 즐겁게 쓰지만, 그 인터뷰는 끔찍했음
Andrew의 답변은 괜찮았는데 전체 분위기가 너무 아첨하는 느낌이었음
- Andrew는 정말 존중하고 Zig도 즐겁게 쓰지만, 그 인터뷰는 끔찍했음
-
Zig를 써보고 싶었지만 언어가 아직 너무 빠르게 변함
릴리스마다 API를 깨뜨리기 때문에, 언어를 배우고 빌드 시스템을 디버깅하고 실제로 만들고 싶은 걸 구현하는 일을 동시에 따라가기 어려웠음
JetBrains 인터뷰를 보고도 다시 시도하고 싶긴 하지만, 아마 1.0이 나올 때까지 기다릴 것 같음 -
오래전부터 이중 프로그래밍이라고 부르는 아이디어를 생각해왔음
스택을 딱 두 언어, 즉 고수준 언어 하나와 저수준 언어 하나로 구성하는 방식임
가능한 한 고수준 언어로 많이 작성하고, 필요할 때만 저수준 언어로 내려가는 구조임
문제는 저수준 언어를 이미 아주 잘 아는 게 아니라면, 저수준 작업을 하기 전에 그 언어에 다시 익숙해져야 할 가능성이 크다는 점임
그래서 C++나 Rust는 C보다 쓰기 어려워지고, 내게는 C가 기본 선택지가 됨. 하지만 C에도 잘 알려진 문제들이 있음
Zig는 긴 공백 뒤에도 다시 잡기 쉬울 만큼 단순하면서, 프로그래밍을 쉽게 해주는 현대적 도구도 갖춰서 그 적절한 지점을 잘 채울 수 있을 것 같음- 그 아이디어는 흥미롭지만, 나는 반대로 생각함
가능한 한 저수준 언어에서 많이 하고, 편의성이 비용을 감수할 만큼 가치 있을 때만 고수준 언어로 올라가는 방식임
Roc이 이걸 가능하게 함. 모든 프로그램은 저수준 언어로 작성된 플랫폼을 갖고, Roc 프로그램은 그 플랫폼이 노출한 API를 사용함
https://www.roc-lang.org/
고수준과 저수준의 균형을 어떻게 잡을지는 물론 각자 선택하면 됨 - SpaCy가 그렇게 개발되는 것으로 알고 있음
모델은 Cython으로 구현하고, 사용자 대상 API는 Python으로 제공함 - C#이 그 목표에 꽤 가까움
- 그냥 Rust처럼 고수준과 저수준 프로그래밍을 둘 다 잘하는 언어 하나를 쓰면 됨
지금은 Rust로 모든 걸 하고 있고, 특히 OCaml 같은 타입 시스템 덕분에 아직 못 하는 걸 찾지 못했음 - 이런 조합으로 흔했던 건 C와 Lua였음
Lua는 임베디드 언어로 의도되어 상호 운용에 좋고, 동시에 고수준이라 쓰기 쉬움
Factorio가 스크립팅 언어로 Lua를 쓰는 데는 이유가 있음
- 그 아이디어는 흥미롭지만, 나는 반대로 생각함
-
Zig 개발에서 마음에 드는 점은, 언어 기능을 추가하는 것보다 도구와 개발자 피드백 루프에 놀라울 정도로 많은 노력을 쏟는다는 점임
새 언어는 한동안 기능 하나가 없어도 살아남을 수 있음
하지만 컴파일, 링크, 의존성 업데이트가 매번 느리게 느껴지면 살아남기가 훨씬 어려움
개발 사이클을 초 단위가 아니라 밀리초 단위로 만들려는 집중은 장기적으로 좋은 베팅처럼 보임 -
“몇 주 안에 0.17.0을 릴리스할 예정”이라니 놀라움
0.16은 1년 넘게 걸리지 않았나?
이렇게 빠른 0.17 릴리스는 예상하지 못했는데, 오늘 알게 되어 매우 반가움- 주로 이 빌드 시스템 변경과 LLVM 22 업그레이드가 0.17.0의 유일한 큰 변경이기 때문임: https://ziglang.org/download/0.16.0/release-notes.html#Roadm...
-
좋은 소식처럼 들림. Zig의 컴파일 시간은 이미 훌륭한데, 이 변경으로 더 좋아질 것 같음
- 내 경험상 그 말은 아직은 대부분 목표에 가깝다고 봄
분명 중요한 목표이고, 달성 방법에 대한 마일스톤도 명확하지만, 실제로는 빈 프로젝트의 최초 컴파일이나direnv allow후 ZLS가 다시 빌드될 때의 고통스러운 대기 시간을 “훌륭하다”고 표현하긴 어려움 - 더미 테스트가 들어간 파일 하나를 만들고
zig test file.zig -OReleaseSafe를 실행해도 내 컴퓨터에서는 몇 초가 걸림
파일을 수정할 때마다 같은 시간이 계속 걸림. 0.16 또는 master를 쓰고 있어서 툴체인이 오래된 것도 아니고 Linux 환경임
Zig 언어 자체는 쓰기 정말 좋지만, 컴파일러와 표준 라이브러리는 충분히 보수적으로 개발되고 있지 않다고 봄
hello world로 시작할 때는 이런 문제를 못 만날 수 있지만, 퍼즈 테스트나 벤치마크를 하려면 최적화된 바이너리를 실행하고 싶어짐
그러면 비교적 작은 코드량을 컴파일하는데도 너무 짜증남
비교하자면 Zig가 Rust/C++/C보다 언어로서는 훨씬 낫다고 생각하지만, Rust/C++/C에서 이런 종류의 문제는 사실상 거의 발생하지 않음. C/C++에서는 clang/gcc/ninja 등을 쓴다고 가정함
같은 컴퓨터에서 Ninja/Python/clang으로 약 1만 줄 C++ 프로젝트를 설정, 빌드(-O2 또는 -O3), 테스트까지 200ms에 할 수 있음 - 다행히 90년대식 컴파일 속도가 천천히 돌아오고 있음
- 내 경험상 그 말은 아직은 대부분 목표에 가깝다고 봄
-
Zig가 Linux 라이브러리 스텁을 내보내는 공식 메커니즘을 갖게 된다면 정말 좋겠음
Zig의 교차 컴파일과 임의 glibc 버전 타깃팅 능력은 순수한 마법임
이 마법을 별도 C++ 빌드 시스템에서 활용하고 있지만, Zig에서 그 라이브러리 스텁을 얻으려면 우회해야 함
공식 출력으로 제공되면 좋겠음 -
굳이 Node.js와 TypeScript 대신 이걸 쓰고 싶어질 이유가 뭐임?
- 이런 사고방식이 부러움. 나도 그랬다면 이미 공개한 앱이 많았을 것 같음
- Zig로 작성된 Ghostty를 잠깐 써보고, JavaScript로 작성된 Hyper 같은 터미널과 비교해보길 권함
- 둘은 완전히 다른 영역용이기 때문 아님?
- 하는 일이 Node와 TypeScript에 잘 맞는다면, 학습이나 호기심 말고는 Zig를 쓸 이유가 없다고 봄
성능의 마지막 한 방울, 메모리 배치와 제어가 필요 없다면 Zig를 쓰는 데는 단점이 더 많음
CRUD나 “엔터프라이즈” 작업, 웹사이트는 Zig의 이점을 거의 못 봄 - 임베디드 시스템에서는 메모리가 부족해서 Node를 실행할 수 없음
컴파일된 Zig 프로그램은 의존성 없이 몇 KB에 불과할 수 있음
저수준 언어로 작성한 배열 접근은 SIMD와 병렬화로 최적화할 수 있고, 같은 작업을 JavaScript로 하는 것보다 몇 자릿수 빠를 수 있음
텍스트 처리, 이미지 조작, 동영상 처리, 해싱 등에서 차이가 큼
JavaScript를 쓰지 않을 이유는 사실상 무한히 많음