Điều gì xảy ra khi bạn gọi make đệ quy kèm tùy chọn -j?
Bài viết phân tích cách GNU Make xử lý các lời gọi make đệ quy khi dùng tùy chọn -j, qua đó chỉ ra sự khác biệt giữa các phiên bản từ 3.81 đến 4.3. Tác giả cũng so sánh các cách truyền jobserver và những cạm bẫy phổ biến khi viết Makefile lồng nhau.
Trong thế giới phát triển phần mềm, make vẫn là công cụ build quen thuộc với hàng triệu lập trình viên, đặc biệt trong các dự án C/C++ và hệ thống nhúng. Nhưng khi bạn gọi make đệ quy — tức là một recipe trong Makefile lại chạy lệnh make — kèm tùy chọn -j để build song song, mọi thứ có thể trở nên rối rắm hơn bạn tưởng.
Một bài viết mới trên blog của Arthur O'Dwyer đã mổ xẻ chủ đề này, so sánh hành vi của GNU Make từ phiên bản 3.81 đến 4.3. Kết quả cho thấy các phiên bản Make xử lý jobserver theo những cách rất khác nhau — và nhiều cách viết Makefile tưởng chừng "chạy được" lại tiềm ẩn rủi ro về hiệu năng.
Vấn đề cốt lõi: jobserver không được chuyển tiếp
Hãy tưởng tượng bạn có một Makefile như sau:
ser:
make -C subdir
Khi chạy make -j4 ser, các phiên bản GNU Make gần đây sẽ cảnh báo:
make[1]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule.
Điều này có nghĩa là jobserver — cơ chế quản lý số lượng tác vụ chạy song song của make — không được truyền xuống tiến trình make con. Kết quả là bạn mất hoàn toàn khả năng build song song, dù đã chỉ định -j4.
Tệ hơn, lệnh make trong recipe còn chạy bất kỳ binary make nào có trong PATH, thay vì đúng binary bạn đã gọi ban đầu.
Ba cách khắc phục, nhưng không hoàn hảo
Tác giả chỉ ra ba cách xử lý, mỗi cách đều có điểm yếu:
- Thêm dấu
+vào đầu dòng recipe:+make -C subdir. Cách này buộc Make truyền jobserver xuống tiến trình con, nhưng bị đánh giá là "ma thuật khó chịu" — tương tự như tiền tố@hay-. - Dùng biến
$(MAKE):$(MAKE) -C subdir. Trông tự nhiên hơn, hoạt động tương đương về mặt kỹ thuật, nhưng cũng không giải quyết triệt để mọi cảnh báo. - Chỉ định tường minh số job trong lời gọi con, ví dụ
make -j2 -C subdir. Cách này giúp bạn kiểm soát được mức song song nhưng dễ gây "quá tải" nếu kết hợp với jobserver của cha.
Sự tiến hóa qua các phiên bản
Bài viết mang đến các bảng so sánh chi tiết về số recipe chạy song song thực tế, tương ứng với từng tổ hợp Makefile và tùy chọn dòng lệnh:
- GNU Make 3.81: Có những dòng được đánh dấu † hành xử không nhất quán giữa các phiên bản, tác giả khuyến nghị tránh dùng vì tính "phi di động".
- GNU Make 4.0: Xuất hiện thêm cảnh báo chi tiết hơn, đồng thời một số cách viết như
parbị giới hạn xuống-j1khi chạy kèm-j2hoặc-j4. - GNU Make 4.2.1 và 4.3: Cảnh báo được cải thiện khi hiển thị rõ giá trị N (
-j0 forced in submake: resetting jobserver mode), và hành vi của các dòngpar,parplus,parmakecũng thay đổi — chúng chuyển sang chế độ "vô hạn" thay vì bị ép về-j1.
Đáng chú ý, cảnh báo jobserver unavailable sẽ biến mất hoàn toàn nếu bạn dùng + hoặc $(MAKE), nhưng cảnh báo về việc "reset jobserver mode" thì vẫn còn.
Bài học cho lập trình viên Việt Nam
Với các dự án phần mềm tại Việt Nam — từ startup đến doanh nghiệp outsource — Makefile lồng nhau vẫn xuất hiện khá phổ biến, đặc biệt trong các hệ thống build đa module hoặc firmware nhúng. Việc hiểu rõ cách jobserver hoạt động giúp bạn:
- Tránh build chậm bất ngờ khi CI/CD tự động gọi
make -j$(nproc)mà không đạt được tốc độ mong đợi. - Chuẩn hóa Makefile trong team, hạn chế dùng các tiền tố "ma thuật" gây khó hiểu cho người mới.
- Kiểm soát phiên bản Make trong Docker image hoặc môi trường build, vì hành vi có thể khác nhau giữa Ubuntu 22.04, Rocky 8 hay macOS.
Tác giả bài viết cũng bày tỏ mong muốn về một hành vi lý tưởng — được gọi là "fantasy" trong bảng so sánh — nơi lời gọi con tự động kế thừa đúng mức song song từ cha. Nhưng cho đến nay, lập trình viên vẫn phải chọn lựa giữa các giải pháp đánh đổi.
Nếu bạn đang duy trì một hệ thống build lớn, đây là lúc nên rà soát lại các recipe gọi make đệ quy — bởi một dấu
+bị thiếu có thể là nguyên nhân khiến pipeline của bạn chạy chậm hơn nhiều so với tiềm năng thực sự của máy chủ.

