Khi nhiều tiến trình cùng muốn CPU, hệ điều hành phải quyết ai chạy bao nhiêu. Công cụ người dùng chỉnh được là nice value — một số từ -20 (giành nhất) tới +19 (nhường nhất), mặc định 0. Trực giác đơn giản: đặt nice thấp thì tiến trình chạy nhanh hơn, đặt nice cao thì nó nhường. Nghe rõ ràng, nhưng cả khi nào nice có tác động lẫn bao nhiêu đều khác trực giác. Tôi đo trong container gcc:13 (10 lõi, ARM) — buộc cạnh tranh bằng cách ghim tiến trình vào một lõi — và phát hiện nice là công cụ chia phần, không phải nút tăng tốc.
nice chia CPU tranh chấp, không tăng tốc
Điểm dễ hiểu sai nhất: nice không làm một tiến trình nhanh hơn — nó chỉ quyết cách chia CPU khi có cạnh tranh. Nếu hệ thống còn lõi rảnh, mọi tiến trình sẵn sàng chạy đều được chạy full, và nice chẳng thay đổi gì. nice chỉ có nghĩa khi CPU bão hòa: nhiều tiến trình muốn chạy hơn số lõi có, và bộ lập lịch phải chọn ai được nhiều thời gian hơn.
Bộ lập lịch Linux (CFS — Completely Fair Scheduler) biến nice thành một trọng số: mỗi bậc nice thay đổi trọng số khoảng 1,25 lần. Tiến trình trọng số cao hơn nhận tỉ lệ CPU cao hơn tương ứng — nhưng vì trọng số là hàm mũ của nice, tác động không tuyến tính theo con số nice.
Để đo, tôi chạy hai tiến trình cùng đua CPU, đếm số vòng lặp mỗi bên làm được trong một khoảng thời gian cố định — tỉ lệ đó là tỉ lệ CPU chúng nhận. Trước hết ghim cả hai vào một lõi (buộc bão hòa).
Đo: 50/50 khi rảnh, 96/4 khi chênh lớn
GHIM 1 LÕI (bão hòa):
nice 0 vs nice 0 : 50 / 50
nice 0 vs nice 5 : 50 / 50 (chênh nhỏ ~ không đổi)
nice 0 vs nice 10 : 75 / 25 (tỉ lệ 3,05×)
nice 0 vs nice 19 : 96 / 4 (tỉ lệ 21,75×)
KHÔNG GHIM (10 lõi rảnh):
nice 0 vs nice 19 : 50 / 50 (nice VÔ NGHĨA!)
Hai kết quả đều đáng chú ý. Thứ nhất, tác động phi tuyến và yếu ở chênh nhỏ: nice 0 so nice 5 vẫn chia 50/50 — chênh 5 bậc gần như không đổi gì. Chỉ khi chênh tới 10 bậc mới thấy lệch rõ (75/25), và chênh 19 bậc mới thành áp đảo (96/4, một bên nhận gần 22 lần bên kia). Tăng nice "một chút" thường chẳng thay đổi gì đáng kể — phải chênh lớn mới có hiệu ứng mạnh.
Thứ hai, và quan trọng hơn: bỏ ghim lõi đi thì nice hoàn toàn vô nghĩa. Hai tiến trình trên 10 lõi rảnh, dù nice chênh nhau tối đa (0 vs 19), vẫn chia 50/50 — mỗi tiến trình được một lõi riêng và chạy full, nice không đụng tới. nice chỉ ra tay khi CPU bị tranh.
Một lần tôi đo hớ: nice không phải nút tăng tốc
Tôi vào đo với hai niềm tin. Thứ nhất: "đặt nice thấp thì tiến trình chạy nhanh hơn". Thứ hai, ngầm hơn: "nice luôn có tác động — cứ chỉnh là thấy khác". Phép đo sửa cả hai.
nice không làm tiến trình nhanh hơn theo nghĩa tuyệt đối — nó chỉ cho tiến trình một phần lớn hơn của cái CPU đang bị tranh. Bằng chứng: trên 10 lõi rảnh, tiến trình nice -0 và nice-19 chạy y như nhau (50/50), vì không có gì để tranh. Nếu tôi tin "nice thấp = nhanh hơn" và đặt một tác vụ nền quan trọng ở nice thấp trên một máy không bão hòa CPU, tôi chẳng được gì — nó vốn đã chạy full. Và ngay cả khi có cạnh tranh, chỉnh nice một chút (5 bậc) gần như không đổi kết quả; phải chênh lớn mới thấy.
Bài học đo lường: một "nút chỉnh" chỉ có tác dụng trong đúng điều kiện của nó — đo ở đúng điều kiện đó, và đo cả đường cong để thấy nó phi tuyến. nice là công cụ chia CPU khi đông, không phải tăng tốc khi rảnh. Nếu chỉ đo ở một điểm (ghim một lõi, một mức nice) rồi khái quát "nice làm nhanh hơn", tôi đã bỏ lỡ cả điều kiện (phải bão hòa) lẫn hình dạng (phi tuyến, yếu ở chênh nhỏ). Đây cùng tinh thần đo tác động đúng cách: một tham số chỉ có ý nghĩa trong ngữ cảnh của nó.
Vì sao điều này quan trọng khi lập trình
Hệ quả đầu tiên: dùng nice để chia CPU khi có tranh chấp, không để tăng tốc một tiến trình đơn lẻ. Đặt một job nền nặng CPU (backup, nén, build) ở nice cao (+10 tới +19) là cách tốt để nó nhường các tác vụ tương tác khi máy bận — nhưng khi máy rảnh nó vẫn chạy full, không bị phạt. Ngược lại, đừng kỳ vọng đặt một tiến trình ở nice thấp sẽ "tăng tốc" nó nếu CPU chưa bão hòa: nó vốn đã có đủ CPU.
Hệ quả thứ hai: muốn nhường thật rõ thì phải chênh nice lớn. Vì tác động phi tuyến (yếu ở chênh nhỏ), tăng nice một job nền từ 0 lên 5 gần như không giúp các tác vụ khác. Muốn nó thật sự lùi lại khi máy bận, đặt nice cao hẳn (15-19). Và nếu cần đảm bảo cứng (job này không bao giờ được đụng CPU khi có việc quan trọng hơn), nice là chưa đủ — cần cơ chế mạnh hơn như cgroup (giới hạn tài nguyên) mà một phần sau của sê-ri sẽ chạm tới.
Hệ quả thứ ba là tinh thần đo lường: một tham số chỉ có ý nghĩa trong điều kiện của nó — đo ở đó, và đo cả đường cong. Con số mang theo: nice CHIA CPU khi có cạnh tranh, KHÔNG làm một tiến trình nhanh hơn — đo được: trên 10 lõi rảnh, nice 0 vs 19 vẫn 50/50 (vô nghĩa), chỉ khi ghim cùng một lõi (bão hòa) mới lệch; và tác động PHI TUYẾN (CFS cho trọng số ~1,25× mỗi bậc): chênh 5 gần như không đổi (50/50), chênh 10 -> 75/25 (3,05×), chênh 19 -> 96/4 (21,75×). Muốn nhường rõ phải chênh lớn. nice là công cụ chia phần khi đông, không phải nút "chạy nhanh hơn".
Thử ba mươi giây
Nếu bạn từng nice -n 10 ./job-nền hay renice một tiến trình để "cho nó nhanh/chậm hơn", hỏi: lúc đó máy có bão hòa CPU không? Nếu số lõi nhiều hơn số tiến trình đang chạy, nice của bạn có thể chẳng làm gì — mọi thứ vốn đã chạy full. Thử một thí nghiệm: chạy hai vòng lặp CPU vô hạn cùng lúc, ghim cả hai vào một lõi (taskset -c 0), rồi renice một cái lên +19 và xem top — bạn sẽ thấy một cái chiếm ~95% và cái kia ~5%. Rồi bỏ ghim (cho chúng dùng cả máy) và xem lại — hai cái lại 50/50 dù nice vẫn chênh. Ba mươi giây đó dạy bạn điều mà "nice = ưu tiên" giấu đi: nice không phải thứ tự tuyệt đối, mà là tỉ lệ chia phần khi phải tranh — và nếu không ai phải tranh, nó im lặng.