VPS

Gradle build chậm: tìm nghẽn và tăng tốc

Thanh Nguyễn Đăng 9 phút đọc 4 khối lệnh

Gradle build chậm: tìm nghẽn và tăng tốc
Mục lục bài viết
  1. Đo trước khi chỉnh
  2. Ba dòng cấu hình cơ bản
  3. Ổ đĩa, chỗ ít người soi tới
  4. Tác vụ tự viết làm hỏng bộ nhớ đệm
  5. Trên máy CI thì khác gì
  6. Đo từng tác vụ dựng trước khi chỉnh
  7. Ba dòng bật tiến trình nền và dựng song song
  8. Hàng chục nghìn tệp nhỏ, việc ổ chậm làm tệ nhất
  9. Câu hỏi thường gặp
  10. Bài viết liên quan

Trả lời nhanh: Build Gradle chậm hầu như luôn nằm ở bốn chỗ, theo đúng thứ tự nên kiểm tra: chưa bật bộ nhớ đệm và chạy song song, cấp bộ nhớ sai cho máy ảo Java, ổ đĩa chậm vì Gradle đọc ghi hàng chục nghìn file nhỏ, và các tác vụ tự viết làm hỏng cơ chế bỏ qua việc đã làm. Đừng đoán, hãy đo trước: một lệnh có sẵn cho bạn biết tác vụ nào ngốn thời gian, và thường kết quả khác xa dự đoán.

Mỗi lần build mất tám phút, một ngày build mười lần là hơn một giờ ngồi chờ, nhân với số người trong đội. Đây là loại chi phí không ai ghi vào báo cáo nhưng ai cũng chịu. Tin tốt là phần lớn dự án cắt được nửa thời gian chỉ bằng cấu hình, chưa cần đụng tới code.

Đo trước khi chỉnh

TerminalĐo trước khi chỉnhbash
# Xem thoi gian tung tac vu./gradlew assembleDebug --profile# Bao cao nam trong build/reports/profile/ # Kiem tra tac vu nao khong duoc bo qua./gradlew assembleDebug --scan

Báo cáo cho bạn ba con số đáng chú ý: tổng thời gian cấu hình, tổng thời gian chạy tác vụ, và danh sách tác vụ ngốn nhiều nhất. Nếu thời gian cấu hình chiếm phần lớn, vấn đề nằm ở các file cấu hình build chứ không ở việc biên dịch.

Chạy hai lần liên tiếp mà không đổi gì cũng là phép thử rất hữu ích. Lần thứ hai đáng lẽ phải nhanh hơn nhiều nhờ bỏ qua các tác vụ đã làm. Nếu nó vẫn lâu bằng lần đầu, nghĩa là cơ chế bỏ qua đang hỏng và đó chính là chỗ cần sửa.

Ba dòng cấu hình cơ bản

TerminalBa dòng cấu hình cơ bảnbash
# gradle.propertiesorg.gradle.caching=trueorg.gradle.parallel=trueorg.gradle.daemon=trueorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g

Bộ nhớ đệm cho phép tái sử dụng kết quả của những tác vụ có cùng đầu vào, kể cả từ lần build trước. Đây là dòng cho hiệu quả cao nhất.

Chạy song song cho phép các mô đun độc lập build cùng lúc. Hiệu quả rõ với dự án nhiều mô đun, không đổi gì với dự án một mô đun.

Tiến trình nền giữ Gradle sống giữa các lượt build nên lần sau không phải khởi động lại máy ảo Java. Trên máy CI chỉ chạy một lượt rồi tắt thì dòng này không giúp gì, còn trên máy làm việc thì rất đáng.

Về bộ nhớ, quy tắc là đặt nhỏ hơn RAM thật và chừa phần cho hệ điều hành. Trên máy 8GB thì 4g là an toàn. Đặt bằng đúng RAM là công thức khiến tiến trình bị hệ điều hành giết giữa chừng với thông báo lỗi vô nghĩa.

Ổ đĩa, chỗ ít người soi tới

Một lượt build Android hoặc Flutter đọc ghi hàng chục nghìn file nhỏ: nguồn, tài nguyên, lớp đã biên dịch, bộ nhớ đệm. Đây đúng là loại thao tác mà ổ đĩa quyết định, chứ không phải CPU.

Cách kiểm chứng nhanh trên máy chủ: chạy lệnh theo dõi mức bận của ổ đĩa trong lúc build. Nếu ổ bận gần như liên tục trong khi CPU chỉ dùng một phần, bạn đã tìm ra nghẽn, và lúc đó thêm nhân xử lý cũng không giúp gì.

Ba việc đáng làm khi nghẽn ở ổ đĩa: chuyển sang máy dùng ổ NVMe; giữ thư mục bộ nhớ đệm của Gradle trên ổ nhanh nhất; và đừng đặt thư mục làm việc trên ổ mạng. Phân tích chi tiết nằm ở bài về ổ đĩa khi build app.

Tác vụ tự viết làm hỏng bộ nhớ đệm

Đây là nguyên nhân phổ biến khiến lần build thứ hai vẫn chậm. Gradle chỉ bỏ qua được một tác vụ khi nó biết đầu vào và đầu ra của tác vụ đó. Tác vụ tự viết mà không khai báo thì luôn bị coi là cần chạy lại.

TerminalTác vụ tự viết làm hỏng bộ nhớ đệmbash
tasks.register("sinhCauHinh") {    inputs.file("cau-hinh-goc.json")      // khai bao dau vao    outputs.file("$buildDir/cau-hinh.json")  // khai bao dau ra    doLast { /* ... */ }}

Hai dòng khai báo đó là toàn bộ khác biệt giữa một tác vụ chạy lại mỗi lần và một tác vụ được bỏ qua khi không có gì đổi.

Một nguyên nhân khác cùng nhóm: đọc giá trị thay đổi theo thời gian, ví dụ thời điểm build hoặc mã commit, rồi ghi vào file nguồn. Mỗi lần build là một giá trị mới, nên mọi thứ phía sau đều phải làm lại. Nếu cần các giá trị đó, hãy đưa chúng vào một file riêng ở bước cuối thay vì trộn vào nguồn.

Trên máy CI thì khác gì

Máy CI thường chạy một lượt rồi tắt, nên vài mẹo trên máy làm việc không áp dụng được:

  • Tiến trình nền không giúp gì, có khi còn tốn thời gian khởi động thừa.
  • Bộ nhớ đệm phải được giữ lại giữa các lượt, nếu không mỗi lượt lại tải và biên dịch từ đầu. Đây là lý do lớn nhất khiến máy tự dựng nhanh hơn dịch vụ dùng chung, như phân tích ở bài runner cho CI mobile.
  • Cấp bộ nhớ theo máy CI chứ không chép từ máy cá nhân. Đây là nguồn của những lượt build hỏng chỉ xảy ra trên CI.

Cuối cùng, đừng quên dọn: thư mục build và bộ nhớ đệm phình rất nhanh, và ổ đầy vào ngày phát hành là sự cố kinh điển của mọi máy build tự dựng.

Đo từng tác vụ dựng trước khi chỉnh

TerminalĐo từng tác vụ dựng trước khi chỉnhbash
# 1. Xem thoi gian tung tac vu, tim cho ton nhat./gradlew build --profile# ket qua nam trong build/reports/profile/ # 2. Kiem tac vu nao khong dung duoc bo nho dem./gradlew build --scan # 3. Do thoi gian thuc te, chay 3 lan lay trung vifor i in 1 2 3; do /usr/bin/time -f "%e giay" ./gradlew assembleDebug; done # 4. Trong luc dung: nghen o vi xu ly hay o diavmstat 2 60    # us cao la vi xu ly; wa cao la cho o diaiostat -x 2 30 # 5. Bo nho dinh diem/usr/bin/time -v ./gradlew assembleRelease 2>&1 | grep "Maximum resident"
Kết quảNghẽn ở đâuSửa gì
Chờ ổ đĩa caoỔ đĩaỔ NVMe, và giữ bộ nhớ đệm giữa các lần dựng
Vi xử lý gần 100%Vi xử lýBật dựng song song, tăng xung nhịp
Bộ nhớ đầy, có tráo trangBộ nhớTăng RAM, hoặc giảm bộ nhớ cấp cho tiến trình dựng
Tiến trình bị dừng giữa chừngHết bộ nhớThêm RAM hoặc đặt vùng nhớ tạm

Ba dòng bật tiến trình nền và dựng song song

  • Bật tiến trình nền giữ giữa các lần dựng. Lần đầu vẫn chậm, các lần sau nhanh hơn nhiều.
  • Bật dựng song song nếu dự án có nhiều mô đun.
  • Bật bộ nhớ đệm kết quả dựng, để tác vụ không đổi thì không chạy lại.
  • Đặt bộ nhớ cấp cho tiến trình dựng theo bộ nhớ máy, không đặt bừa. Đặt quá cao là máy hết bộ nhớ và tiến trình bị dừng.

Hàng chục nghìn tệp nhỏ, việc ổ chậm làm tệ nhất

Việc dựng ứng dụng đọc và ghi hàng chục nghìn tệp nhỏ, đúng loại thao tác mà ổ chậm xử lý tệ nhất. Với dự án lớn, chuyển sang ổ NVMe thường cho cải thiện lớn hơn mọi tinh chỉnh cấu hình.

Trên máy chạy tác vụ tự động, thêm một điểm nữa: nếu máy không giữ bộ nhớ đệm giữa các lần chạy thì mỗi lần đều tải lại toàn bộ thư viện. Đây là nguyên nhân số một khiến dựng trên máy tự động chậm hơn dựng trên máy cá nhân. Xem bài về máy chạy tác vụ tự dựng.

Câu hỏi thường gặp

Vì sao build lần hai vẫn chậm như lần đầu?

Vì cơ chế bỏ qua tác vụ đang hỏng, thường do một tác vụ tự viết không khai báo đầu vào ra, hoặc do có giá trị thay đổi mỗi lần build được ghi vào nguồn. Chạy với chế độ xem chi tiết để biết tác vụ nào không được bỏ qua.

Nên cấp bao nhiêu bộ nhớ cho Gradle?

Nhỏ hơn RAM thật và chừa phần cho hệ điều hành cùng các tiến trình khác. Trên máy 8GB thì khoảng 4g là hợp lý. Cấp quá tay khiến hệ điều hành giết tiến trình, và thông báo lỗi khi đó rất khó lần ra nguyên nhân.

Tăng nhân CPU có làm build nhanh hơn không?

Chỉ khi nghẽn nằm ở biên dịch. Nếu ổ đĩa bận gần như liên tục trong lúc build thì nghẽn ở đĩa, và thêm nhân không giúp gì. Hãy đo trước để biết mình đang nghẽn ở đâu.

Máy build nên cấu hình thế nào?

Từ 4 vCPU và 8GB cho dự án vừa, lên 8 vCPU và 16GB nếu build song song nhiều nhánh. Ưu tiên ổ NVMe vì mỗi lượt build đọc ghi rất nhiều file nhỏ, và chừa dung lượng rộng cho bộ nhớ đệm.

Chia sẻ:Facebook

Thanh Nguyễn

TND

Tư vấn bản quyền, VPS và máy chủ kèm hóa đơn VAT, hợp đồng đầy đủ cho doanh nghiệp.