Tốc độ build của Zig đang nhanh hơn
(mitchellh.com)- Việc phát hành Zig 0.15.1 đã cải thiện đáng kể tốc độ biên dịch so với phiên bản trước
- Kết quả đo thời gian build thực tế trong dự án Ghostty cho thấy thời gian chạy tổng thể đã được rút ngắn
- Dù vẫn còn sử dụng LLVM ở một phần, nhưng khi áp dụng backend riêng, kỳ vọng cải thiện tốc độ thêm nữa là rất lớn
- Biên dịch tăng dần (incremental compilation) vẫn chưa được triển khai hoàn chỉnh, nhưng lợi ích hiệu năng đã xuất hiện ngay cả trong các bản build từng phần
- Trong tương lai, khả năng cao sẽ hiện thực hóa được môi trường build nhanh hơn nữa cùng trải nghiệm phát triển tốt hơn
Tổng quan
Bắt đầu từ phát biểu của Andrew Kelley rằng "trình biên dịch quá chậm nên sinh ra lỗi", Zig trong nhiều năm qua đã theo đuổi nhiều cải tiến về cấu trúc với mục tiêu rút ngắn thời gian biên dịch
- Nhóm Zig đã nỗ lực loại bỏ LLVM, phát triển backend sinh mã riêng, xây dựng linker riêng, và cuối cùng là hiện thực hóa biên dịch tăng dần
- Thành quả của quá trình phát triển dài hạn này bắt đầu thể hiện rõ ở phiên bản Zig 0.15.1, và sự thay đổi về thời gian build đã được đo đạc, chia sẻ trên dự án thực tế (Ghostty)
Tốc độ biên dịch Build Script
- Zig 0.14: 7 giây 167ms
- Zig 0.15: 1 giây 702ms
Đây là thời gian build của chính script build.zig, và là chi phí ban đầu phải trả mỗi lần trong môi trường build mã nguồn mới
- Tần suất biên dịch lại build script được giữ ở mức thấp, nhưng nó ảnh hưởng trực tiếp đến trải nghiệm người dùng khi lần đầu tự build dự án
Build toàn bộ binary không dùng cache (Ghostty)
- Zig 0.14: 41 giây
- Zig 0.15: 32 giây
Đây là tổng thời gian build binary, bao gồm cả thời gian build script build
- Với Zig 0.15, còn có thêm hiệu quả cải thiện tốc độ khoảng 2 giây, và sự khác biệt ban đầu cũng rất rõ ràng theo thời gian thực tế
- Hiện tại backend x86_64 riêng vẫn chưa được tận dụng hoàn toàn, và phần lớn vẫn tiếp tục dùng LLVM
- Trong tương lai, khi Ghostty được build hoàn toàn bằng backend riêng, thời gian được dự đoán có thể giảm xuống dưới 25 giây (chỉ còn khoảng một nửa so với trước)
Build tăng dần (file thực thi Ghostty)
- Zig 0.14: 19 giây
- Zig 0.15: 16 giây
Đây là thời gian cần để build lại sau một thay đổi một dòng có ý nghĩa (thêm lệnh gọi log trong mã giả lập terminal)
- Đây là bản build từng phần trong tình huống build script và đồ thị phụ thuộc đã ở trạng thái cache
- Dù tính năng biên dịch tăng dần vẫn chưa được triển khai hoàn hảo, cải thiện hiệu năng đã thể hiện rất rõ
- Nếu loại trừ LLVM đang được sử dụng, còn có khả năng rút xuống khoảng 12 giây
- Khi build tăng dần thực thụ được triển khai trong tương lai, thậm chí có thể kỳ vọng build ở mức mili giây
Build tăng dần (libghostty-vt)
- Zig 0.14: 2 giây 884ms
- Zig 0.15: 975ms
Đây là phép đo thời gian chỉ build lại một phần libghostty-vt sau thay đổi một dòng
libghostty-vtcó thể được build hoàn toàn bằng backend x86_64 riêng, nên các cải tiến của Zig được phản ánh trực tiếp mà không bị ảnh hưởng bởi LLVM- Ngay cả khi chưa có biên dịch tăng dần, đạt thời gian build dưới 1 giây vẫn là một bước tiến đáng kể
- Hiệu quả được nâng cao trong quy trình làm việc của lập trình viên nhờ trải nghiệm phản hồi gần như tức thì
- Các backend x86_64 và aarch64 đang dần ổn định hơn, và có khả năng sẽ được áp dụng cho toàn bộ Ghostty trong vài tháng tới
Hiện trạng cải thiện tốc độ build
- Bản build Ghostty dùng Zig 0.15.1 hiện đã nhanh hơn rõ rệt ở mọi hạng mục đo đạc
- Dù backend riêng và biên dịch tăng dần vẫn chưa hoàn thiện, riêng những kết quả hiện tại cũng đã đủ ấn tượng
- Trong 1–2 năm tới, có thể kỳ vọng các kết quả cải thiện tốc độ còn đột phá hơn nữa
- Có thể cảm nhận rõ rằng việc chọn Zig là một lựa chọn hợp lý từ góc độ tốc độ build
1 bình luận
Ý kiến Hacker News
Khi tốt nghiệp trung học năm 1995, tôi từng biên dịch lại "Borland Pascal version Turbo Vision for DOS" trên Intel 486, và giờ lại được trải nghiệm tốc độ biên dịch nhanh đến mức tương đương như vậy
Turbo Vision là một framework cửa sổ TUI, được dùng để phát triển IDE Borland Pascal và C++
Có thể xem nó như một chế độ ký tự triển khai IDE của JetBrains trong 10MB thay vì 1000MB
Wikipedia về Turbo Vision
LLVM là một kiểu cái bẫy
Khởi động bootstrap thì rất nhanh và gần như được miễn phí đủ loại optimization pass cùng hỗ trợ đa nền tảng, nhưng lại đánh mất khả năng tinh chỉnh hiệu năng ở giai đoạn tối ưu cuối hoặc giai đoạn linking
Tôi nghĩ Cranelift sắp được bật trong Rust
Nhưng cũng chính vì Rust ban đầu chọn LLVM nên mới có được vị thế hiện nay
Go từ sớm đã quyết định tự quản lý code generation và link thay vì giao ra bên ngoài, và đang hưởng lợi rất nhiều từ lựa chọn đó
Tôi khó đồng ý với lập luận rằng LLVM là cái bẫy, và Rust lại chính là ví dụ tốt
Thực tế phần tạo mã bằng LLVM chỉ chiếm một phần rất nhỏ trong compiler, và nếu muốn thay thế thì có thể đổi sang codegen_cranelift hay codegen_gcc
Việc phụ thuộc vào SIMD vendor intrinsic đúng là một vấn đề "lock-in", nhưng đây là vấn đề thuộc về cấu trúc ngôn ngữ
Với đa số ngôn ngữ, bắt đầu bằng backend LLVM là lựa chọn hợp lý
Với các ngôn ngữ tương tự C/C++, chỉ cần pipeline mặc định của LLVM cũng tối ưu khá tốt, còn ngôn ngữ có đặc điểm khác thì sẽ tự viết pipeline tối ưu riêng
Các trường hợp như Go, vốn đã tích hợp backend riêng ngay từ đầu, đúng là trông có vẻ thành công, nhưng đó không hẳn là yếu tố khác biệt đặc biệt, và việc tự triển khai đi kèm chi phí cơ hội khá lớn
Compiler của Go và Ocaml thực sự rất nhanh
Họ đã xây dựng thư viện riêng tử tế ngay từ đầu, và giờ không còn lý do gì phải chịu thiệt về tốc độ
Tôi không muốn làm việc trong môi trường mà mỗi lần compile mất hơn 1 phút
Tôi ước mỗi dự án có một compiler riêng cho môi trường
dev, rồi chỉ dùng thứ nặng như llvm cho bản build cuối cùngNếu một ngôn ngữ dựa trên LLVM mà nhắm tới việc thay thế C++, thì rốt cuộc nó vẫn sẽ phụ thuộc vào C++
Ngôn ngữ phải có khả năng bootstrap bằng chính nó
Ban đầu có thể dùng các công cụ tiện lợi, nhưng chúng chỉ là cách tiết kiệm thời gian chứ không phải thứ bắt buộc
Tôi hoàn toàn đồng cảm với lựa chọn mà đội ngũ Go đã đưa ra
Tôi mong Cranelift tiếp tục phát triển bền vững
LLVM hiện nay bị phân nhánh tràn lan theo từng hãng CPU, mỗi nhánh lại gói các cải tiến riêng cho từng CPU trong các package đóng
Nếu dùng frontend của ngôn ngữ khác hoặc gặp bug compiler thì tình hình sẽ cực kỳ gian nan
Có người đặt câu hỏi rằng ngôn ngữ có thể tự do chuyển sang backend khác, vậy sao lại có thể gọi đó là cái bẫy?
Nếu tốc độ compile cản trở việc phát triển, thì tại sao không làm interpreter?
Tốc độ chạy và tốc độ compile về bản chất là độc lập với nhau
Nếu dùng interpreter thì cũng dễ làm thêm các công cụ phát triển như instrument code hay điều khiển runtime
Có một số rất ít trường hợp phải debug đúng binary RELEASE đã tối ưu, nhưng trong đa số tình huống thì interpreter hoặc bản build DEBUG là đủ
Tôi nghe nói Rust được xếp theo thứ tự an toàn, hiệu năng, tính tiện dụng; còn Zig là hiệu năng, tính tiện dụng, an toàn
Từ góc nhìn đó, việc cải thiện tốc độ build là điều thuyết phục, nhưng interpreter lại là lựa chọn phù hợp hơn khi ưu tiên hàng đầu là tính tiện dụng
Tôi thích cách tiếp cận của Julia
Trong môi trường tương tác đầy đủ, interpreter thực chất biên dịch mã rồi chạy ngay lập tức
Các môi trường Common Lisp như SBCL cũng tương tự
Nếu nhìn ở cực đoan thì tốc độ chạy và tốc độ compile là độc lập
Giữa hai đầu đó có một "vùng có thể thỏa hiệp", nơi có thể làm compiler nhanh hơn mà không làm giảm hiệu năng của file thực thi
Có người cho rằng lĩnh vực game không phải là trường hợp đặc biệt
Tốc độ compiler tốt nhất từ trước đến nay với tôi là TCC (tác phẩm của Fabrice Bellard)
Nó áp đảo về tốc độ dù không có multithread hay tối ưu phức tạp
Tôi dùng Clang cho bản release, nhưng hiệu năng code generation của TCC cũng không tệ
Tôi nghĩ Delphi còn biểu đạt tốt hơn, an toàn hơn, mà tốc độ compile vẫn rất nhanh
Compiler của Go cũng khá nhanh
Tôi từng nghĩ DMD là "tiêu chuẩn vàng" vì có thể compile cả C lẫn D mà vẫn nhanh
Tôi hy vọng TCC sẽ hỗ trợ C23
Không có thứ gì là "tiêu chuẩn vàng" duy nhất
vlang cũng nhanh đến mức chỉ mất vài giây để biên dịch lại, và compiler của Go cũng cực nhanh
Ngoài ra còn có các kỹ thuật như build caching để tránh phải biên dịch lại ngay từ đầu, nên đây không phải đặc quyền của riêng ai
Khi build app bằng zig, tôi rất hài lòng với incremental build
Có thể build một static binary duy nhất dùng nhiều thư viện như SQLite, luau nhanh gần bằng Go
Tuy vậy, compiler self-hosted hiện vẫn còn khá nhiều bug
Ví dụ SQLite vẫn phải dùng llvm, có thể xem issue liên quan ở đây
Tôi tò mò Zig có thể tích hợp tốt với các hệ thống build như Bazel, Buck2 hay không
Script build của Zig là Turing-complete, nên tôi lo trong các hệ thống kiểu này việc caching và tự động hóa build sẽ không dễ
Lý do này cũng giống như việc trong Rust người ta thích thư viện dùng được mà không cần
build.rshơnTôi cũng muốn biết thư viện Zig có thường xuyên có custom build hay không
Script build của Zig hoàn toàn là tùy chọn
Ngay cả khi không có
build.zigthì vẫn có thể build/chạy trực tiếp từng file nguồnBạn có thể gắn Zig vào bất kỳ workflow nào dùng GCC hay Clang
Nhân tiện, Zig cũng hoạt động như một bản thay thế cho C compiler
Bài viết liên quan
Ví dụ tích hợp Bazel với Zig là rules_zig
Dự án thực tế ZML cũng đang dùng nó
Dự án ZML
Tôi tò mò việc compile và code generation của Zig so với TPDE thế nào
Nghe nói nó nhanh hơn LLVM -O0 khoảng 10~20 lần, nhưng có vẻ vẫn có giới hạn
Tôi cho rằng chiến lược của Zig khá táo bạo
Nhưng tôi vẫn băn khoăn liệu dùng backend LLVM cho đến bây giờ có còn đúng không
LLVM có sức cạnh tranh về tốc độ compile và hỗ trợ nền tảng, nhưng về khả năng sinh mã máy tối ưu thì gần như không có đối thủ
Chỉ bản build Release mới dùng backend LLVM, còn bản debug thì mặc định dùng cách self-hosted trên những nền tảng được hỗ trợ
Build debug được lặp lại nhiều lần trong quá trình test thực tế, nên cách này hợp lý hơn
Tôi không quá đồng cảm với sự ám ảnh về hiệu năng compiler
Rốt cuộc đây là bài toán đánh đổi giữa optimization pass (inline, loại bỏ dead code, v.v.) và thời gian compile
Compiler không tối ưu thì chỉ nhanh lên theo tuyến tính, còn muốn tối ưu sâu hơn thì thời gian tiêu tốn luôn tăng mạnh
"Sinh ra assembly xuất sắc" thực ra không phải vấn đề quá quan trọng
Theo Proebsting's Law, sự tiến bộ của công nghệ compiler chậm hơn rất nhiều so với mức tăng hiệu năng phần cứng
Kết luận là chỉ cần các tối ưu đơn giản và nhanh cũng đã đủ thực dụng
LLVM cũng không phải tối ưu tuyệt đối, và khi so với tốc độ compile thì sớm muộn cũng chạm trần
Tôi đang làm một thư viện Java bọc thư viện C bằng JNI, nhưng việc build dynamic library cho từng nền tảng quá phiền phức nên đang cân nhắc dùng zig để build đa nền tảng
Nếu chỉ là một shim đơn giản, thì trên Java mới dùng Panama và jextract có lẽ sẽ tốt hơn
Zig tích hợp sẵn header, mã nguồn libc và cả LLVM riêng, nên cross-compile cực kỳ dễ
Dễ đến mức gần như không phải lo gì cả
Có người thắc mắc vì sao Ghostty hiện vẫn chưa build được bằng backend self-hosted x86_64
Compiler Zig bị crash do bug
Đây vẫn là công nghệ mới nên khá phức tạp, nhưng chắc sớm được xử lý thôi
Phần lớn người dùng Ghostty đang ở nền tảng aarch64