What the Fork - Công cụ trực quan hóa build C/C++/Rust theo thời gian thực
(danielchasehooper.com)- What the Fork là một công cụ đa nền tảng giúp trực quan hóa theo thời gian thực nhiều quy trình build khác nhau như C/C++/Rust
- Có thể dễ dàng xác định các vấn đề mang tính cấu trúc của hệ thống build hiện có như thiếu xử lý song song, quy trình kém hiệu quả
- Hoạt động với mọi hệ thống build và ngôn ngữ lập trình, hỗ trợ nhiều công cụ build như make, ninja, gradle, zig, cargo
- Thông qua giám sát system call, công cụ trực quan hóa thời gian thực thi, lệnh và quan hệ phụ thuộc của từng tiến trình dưới dạng các hộp
- Đây là công cụ rất hữu ích cho tối ưu hóa build, phân tích điểm nghẽn và cải thiện hiệu năng CI
Giới thiệu và bối cảnh
- What the Fork là công cụ trực quan hóa build theo thời gian thực được phát triển để chẩn đoán trực quan nguyên nhân khiến build chậm
- Với những dự án như LLVM, tốc độ biên dịch có thể chậm đơn giản vì lượng mã quá lớn, nhưng phần lớn các bản build lại mất thời gian không cần thiết do cấu hình kém hiệu quả
- Trước đây, rất khó tự kiểm tra trực tiếp các vấn đề của build hoặc nhìn ra các vấn đề mang tính cấu trúc chỉ trong một cái nhìn, vì vậy cần có công cụ như thế này
- Công cụ này được thiết kế đa nền tảng và có thể áp dụng cho mọi hệ thống build và ngôn ngữ
Tính năng chính và cách dùng
- What the Fork không chỉ là một system profiler đơn thuần mà là công cụ chuyên dùng để chẩn đoán các vấn đề đặc thù của build
- Ví dụ có thể phát hiện việc không dùng cờ
-jkhi chạy make, thời gian bị dồn vào một tệp hoặc một bước biên dịch cụ thể, hay các lệnh lẽ ra có thể chạy song song nhưng lại bị thực thi tuần tự - Đặc biệt hiệu quả trong việc phân tích và tối ưu hiệu năng clean build trong môi trường CI
- Cách dùng là thêm lệnh
wtfvào trước lệnh build để chạy (ví dụ:wtf make,wtf cargo build,wtf npm run build) - Khi build bắt đầu, giao diện UI sẽ chạy và cập nhật theo thời gian thực trạng thái tiến hành của từng tiến trình
UI và cách trực quan hóa
- Mỗi tiến trình build được hiển thị trên timeline dưới dạng hộp, với màu sắc để phân biệt loại
- Quan hệ cha-con giữa các tiến trình được thể hiện bằng cấu trúc lồng nhau
- Ở bảng phía dưới, công cụ hiển thị thời gian thực thi, thư mục làm việc và toàn bộ tham số dòng lệnh của tiến trình đang được chọn
Nguyên lý hoạt động
- Một bản build là sự kết hợp của nhiều tiến trình khác nhau (ví dụ:
bash,clang,ld) - Các bản build quy mô lớn sử dụng nhiều công cụ build như
cargo,make,bazel,gradle,xcodebuild, và trên thực tế các công cụ này thực hiện rất nhiều lệnh, phụ thuộc, bộ nhớ đệm và tác vụ lập lịch - Chỉ nhìn vào đầu ra terminal thì không thể nắm được các tiến trình lồng nhau (ví dụ:
ldđượcclanggọi nội bộ) cùng cấu trúc thời gian chi tiết - Để làm điều đó, công cụ sử dụng các system call phát hiện việc bắt đầu và kết thúc tiến trình theo từng hệ điều hành (macOS: Endpoint Security API, Linux: ptrace(), Windows: Event Tracing for Windows)
- Với cách này, có thể khôi phục toàn bộ quá trình build và timeline, đồng thời xác định đường thực thi và thời gian của từng giai đoạn
- Ngoài build, công cụ còn có thể được dùng để theo dõi nhiều subprocess khác
Trường hợp thực tế và kết quả quan sát
- Nhiều kỹ sư (thuộc Delta, Mozilla, Apple) đã áp dụng công cụ này vào dự án thực tế và phát hiện những vấn đề không ngờ tới
- Ví dụ 1: Trong một dự án mã nguồn mở dùng Cargo, các tệp được biên dịch tuần tự, qua đó xác nhận thiếu tính song song (trên CPU 10 lõi có khả năng cải thiện tốc độ hơn 10 lần)
- Ví dụ 2: Trong bản build LLVM dùng Ninja, tất cả các lõi CPU đều thực hiện công việc song song hiệu quả, đạt hiệu suất build lý tưởng
- Ví dụ 3: Trong một dự án dựa trên CMake, phát hiện cấu trúc kém hiệu quả khi cmake/make/clang được chạy lồng nhau và việc kiểm tra lại phiên bản Xcode/OS bị lặp tới 85 lần (trong khi công việc thực sự chỉ chiếm một phần rất nhỏ)
- Ví dụ 4: Trong một dự án Objective-C lớn dùng xcodebuild, phát hiện thiếu xử lý song song ở giai đoạn cuối của build và tồn tại 6 giây không hoạt động trước khi build bắt đầu (trong khi ninja bắt đầu biên dịch gần như ngay sau 0,4 giây)
- Ví dụ 5: Khi Zig biên dịch Orca Project, thứ tự build các phụ thuộc được quyết định ngẫu nhiên, khiến hiệu quả xử lý song song thay đổi tùy may rủi. Quan sát thấy một số phụ thuộc bị đẩy xuống chạy ở cuối làm giảm tính song song
- Ví dụ 6: Trong dự án GitHub CLI dùng make/go, thời gian tải phụ thuộc khá lớn. Nếu giảm số lượng phụ thuộc, tốc độ build được kỳ vọng sẽ cải thiện
Hiệu quả sử dụng và giới hạn
- Thông qua phân tích timeline trực quan, có thể phát hiện các điểm nghẽn bất ngờ, sự lặp lại phụ thuộc không cần thiết và những khu vực thiếu tính song song
- Có thể nhanh chóng xác định các điểm cần cải thiện về mặt cấu trúc như vấn đề phụ thuộc, tái thực thi không cần thiết hoặc sự kém hiệu quả của một công cụ cụ thể, từ đó trực tiếp tối ưu hiệu năng build
- Việc xem toàn bộ câu lệnh của tiến trình cũng cho phép phân tích chi tiết hơn
Chương trình beta
- What the Fork hoạt động trên Windows, Linux và macOS
- Cá nhân và đội nhóm muốn gửi phản hồi có thể đăng ký tham gia beta riêng tư (có cung cấp liên kết Google Form)
3 bình luận
Lệnh này buồn cười quá haha, lại còn là
wtfnữa chứ…Sẽ tuyệt nếu nó được phát hành dưới dạng mã nguồn mở.
Ý kiến trên Hacker News
Hiện đang bị mắc kẹt trong môi trường chỉ dùng được CMake, GCC và Unix Make nên gần như không thể có được thông tin chi tiết về lý do build chậm, bản build lại còn hỗn loạn với các bước build phức tạp như sao chép file từ source sang thư mục build, nhiều ngôn ngữ khác nhau (C, C++, Fortran, Python), các bước CMake tùy biến, v.v., nếu công cụ này hoạt động tốt ngay cả trong môi trường hỗn độn như vậy thì có lẽ sẽ học được rất nhiều điều
tsoding đã viết một thư viện C header đơn cho build đa nền tảng tại https://github.com/tsoding/nob.h, chỉ cần
cc, và có thể dùng công cụ profiling của GDB để xem các bước build, tôi thấy đây là một ý tưởng hay, có thể không hợp với tác giả bài viết này nhưng nếu phải xử lý nhiều ngôn ngữ thì Nix là một công cụ build rất tuyệtTôi đã tự làm một công cụ tracing/profiling thời gian biên dịch bằng plugin GCC, nếu ai quan tâm thì xem tại đây: https://github.com/royjacobson/externis
Khi tôi thử giảm thời gian biên dịch cho game engine của mình, tôi từng dùng kích thước đầu ra đã biên dịch như một thước đo thay thế, vì thời gian đồng hồ thực quá thiếu ổn định nên việc đo kích thước nhị phân cho ra kết quả giống nhau giữa các lần build, thậm chí trên các máy khác nhau, lại dễ xử lý hơn, không khớp 100% nhưng trên thực tế vẫn hữu ích
Tôi cũng gặp vấn đề tương tự, đôi khi thấy CMake biên dịch lại cả những file không hề sửa, ví dụ chỉ chỉnh một chút trong
.cppmà không thay đổi interface nhưng vẫn thấy những object hoàn toàn độc lập bị biên dịch lại, đôi lúc tôi tự hỏi liệu CMake có đang tạo ra các dependency chặt hơn mức cần thiết so với file thực tế hay không, khiến thời gian build dài ra một cách vô íchTôi muốn đề xuất với tác giả blog rằng nên đặt ảnh gif ghi lại quá trình build ứng dụng macOS ở đầu trang, ngay dưới phần header, cách trình bày sẽ dễ xem hơn nếu cho thấy kết quả tạo ra trước rồi mới giải thích sau
Tôi thực sự thích dự án này, năm 2018 tôi từng thử làm điều tương tự bằng strace và dtruss, cùng với https://buildinfer.loopperfect.com/, để tự động tạo file BUCK chẳng hạn, rồi trực quan hóa bằng graphviz, perfetto.dev và các công cụ khác, hơi tiếc là tôi chưa đóng gói được thành sản phẩm chính thức nhưng nó đã giúp ích rất nhiều trong tư vấn, cả cho việc chẩn đoán nguyên nhân lẫn chuyển sang BUCK/Bazel, gần đây tôi lại quay lại xem xét theo hướng áp dụng rộng hơn, dù cách này cũng có những thách thức kỹ thuật mang tính bản chất: nếu log system call được ghi ra đĩa thì dung lượng có thể phình lên hàng chục đến hàng trăm GB (ví dụ: llvm là 50GB, có trường hợp hơn 100GB), đồng thời phải xử lý tốt cả những bước build liên quan tới https, IPC, v.v. (trước đây tôi từng có một khách hàng dùng Perl để kéo code từ Firebird DB mỗi lần build), và vì đây là kiểu phân tích lúc runtime nên cũng phải lặp lại việc phân tích cho từng cấu hình build
LD_PRELOADhay lọc bằng eBPF không?Tôi nghĩ điều này rất tuyệt, tôi luôn cảm thấy có rất nhiều vấn đề bị bỏ sót chỉ vì không có kiểu trực quan hóa như thế này, nếu 10 năm trước tôi có công cụ như vậy khi tối ưu hệ thống build của Mozilla thì đã rất hữu ích, tôi cũng ước bài viết nói rõ hơn về những vấn đề thực tế mà nó đã phát hiện ra
Trong các dự án C++ được quản lý bằng CMake, tôi đã dùng ninjatracing và
-ftime-tracecủa Clang khá thành công để trực quan hóa hiệu năng build, ngoài ra nếu dùng thêm ClangBuildAnalyzer thì có thể phân tích chi tiết hơn compiler đang tốn thời gian ở đâuTrông thật tuyệt, không biết có kế hoạch mã nguồn mở không, tôi cũng đang làm một thứ tương tự nên rất muốn hợp tác
Nếu dùng trình biên dịch Visual C++ trên Windows thì tôi cũng khuyên dùng vcperf, nó được tích hợp sẵn trong VS2022 hoặc có thể tự build từ GitHub để dùng, tôi đã áp dụng nó cho cả UBT lẫn các dự án được tạo bằng CMake, không nhớ rõ liệu nó có cho biết trực tiếp chất lượng song song hóa build hay không, nhưng có thể xem thông tin frontend của compiler rất dễ, đặc biệt là để tìm ra các header được include thường xuyên hoặc vốn dĩ rất nặng
Một điểm quan trọng nhưng thường bị bỏ qua là logic build được “baked in” (tính sẵn) trong build system không thực sự theo dõi chi tiết những thay đổi nào sẽ ảnh hưởng tới nó, ví dụ trong ninja thì một phần logic build được nạp sẵn nên nó rất nhanh, tôi đã benchmark full build của Xerces-C++ bằng ninja (cấu hình bằng CMake) và build2 (một công cụ xử lý cả cấu hình lẫn theo dõi thay đổi ngay trong quá trình build), ninja mất 3.23 giây, build2 mất 3.54 giây, nếu lặp lại build trong khi giữ lại một phần file do CMake tạo thì thời gian giảm còn 3.28 giây, còn riêng bước cấu hình của CMake là 4.83 giây, tính toàn bộ stack thì build CMake+ninja thực tế mất khoảng 8 giây, đây thường mới là khoảng thời gian người ta thực sự phải chịu khi dùng nó như một thư viện
Tôi từng thử cách tương tự là chạy Instruments trong lúc build để xem tiến trình nào làm gì vào thời điểm nào, nhược điểm là nếu build lâu thì Instruments bị giật lag, lại không có lọc theo cây tiến trình nên khá bất tiện, nhưng khi tối ưu mạnh thời gian build mã iOS ở Twitter thì nó đã giúp rất nhiều, gần đây tracing “All Processes” của Instruments lại bị hỏng nên giờ không thể dùng cách này nữa
Trông thật ngầu, không biết hiện giờ đã có bản macOS nào có thể thử ngay chưa, tôi muốn dùng thử cho cả công việc Rust lẫn C++/Swift
Sau khi sửa xong lỗi, chúng tôi không có kế hoạch phát hành bản macOS cho nhóm beta tester tiếp theo, nhưng nếu bạn đăng ký ở cuối bài viết và nhắc tới bình luận này thì tôi sẽ chắc chắn đưa bạn vào nhóm beta
Có vẻ như hiện vẫn chưa có bản phát hành công khai cho bất kỳ OS nào, mới chỉ ở trạng thái cho đăng ký early access mà thôi