Bài 36.228 phút đọc
Mức tối ưu và LTO
Sau bài này bạn sẽ làm được
- Phân biệt sáu mức tối ưu và khi nào dùng cái nào
- Đo được rằng -O2 nhanh hơn -O0 trên mã thật
- Giải thích vì sao -O3 không phải luôn nhanh hơn -O2
- Biết -march=native đánh đổi gì
Tối ưu là chỗ dễ mê tín nhất trong C. Bài này thay việc đoán bằng việc đo: một vòng lặp thật, năm mức tối ưu, và những con số cho thấy khi nào mỗi mức đáng dùng.
#Sáu mức
| Mức | Ý nghĩa | Dùng khi |
|---|---|---|
| -O0 | Không tối ưu, mặc định | Gỡ lỗi: mã khớp nguồn từng dòng |
| -O1 | Tối ưu cơ bản, dịch nhanh | Muốn nhanh hơn O0 mà vẫn dịch nhanh |
| -O2 | Tối ưu đầy đủ, mặc định phát hành | Bản phát hành, gần như luôn |
| -O3 | Thêm vector hoá và nội tuyến mạnh | Chỉ khi đo được là nhanh hơn O2 |
| -Os | Tối ưu kích thước | Hệ nhúng, bộ nhớ hạn chế |
| -Og | Tối ưu nhưng vẫn gỡ lỗi được | Gỡ lỗi mà O0 quá chậm |
#Đo trên mã thật
cham.c
#include <stdio.h>
#include <stdlib.h>
static long tinh(long n) {
long s = 0;
for (long i = 0; i < n; ++i)
for (long j = 0; j < 1000; ++j)
s += (i * j) % 7;
return s;
}
int main(int argc, char **argv) {
long n = (argc > 1) ? atol(argv[1]) : 50000;
printf("%ld\n", tinh(n));
return 0;
}terminal
# Dich moi muc, chay 3 lan, lay lan nhanh nhat, n = 100000
for o in O0 O1 O2 O3 Os; do gcc -std=c11 -$o cham.c -o cham_$o.exe; done
# do bang thoi gian tuong (best of 3)
-O0: 300 ms -O1: 203 ms -O2: 181 ms -O3: 162 ms -Os: 295 ms
terminal
# Vi sao O2 nhanh: xem tinh() con bao nhieu lenh
gcc -std=c11 -O0 -S cham.c -o - | sed -n '/^tinh:/,/ret/p' | grep -c '^\s\+[a-z]'
39
gcc -std=c11 -O2 -S cham.c -o - | sed -n '/^tinh:/,/ret/p' | grep -c '^\s\+[a-z]'
0
#O3 không luôn nhanh hơn
Đoán
# "O3 la cao nhat nen chac nhanh nhat"
gcc -O3 -march=native -funroll-all-loops -ffast-math app.c -o app
# Chong het co le vao, khong do, va co the:
# - cham hon O2
# - file to gap doi
# - -ffast-math lam ket qua so thuc SAI
# - -march=native lam binary khong chay may khac
Đo
# Do tren dung tai cong viec that
for o in O2 O3; do
gcc -std=c17 -$o app.c -o app_$o
echo "== -$o =="
for r in 1 2 3; do
time ./app_$o du_lieu_that.dat > /dev/null
done
done
# Roi giu cai nhanh hon TREN MAY DICH VU THAT, khong phai may ban.
#LTO
Tối ưu lúc liên kết
Link Time Optimization. Bình thường mỗi tệp
.c được tối ưu riêng, nên trình biên dịch không nội tuyến được một hàm nằm ở tệp khác. LTO hoãn phần tối ưu tới bước liên kết, khi đã thấy toàn bộ chương trình, nên nó tối ưu xuyên tệp.# Bat LTO o CA buoc dich LAN buoc lien ket
gcc -O2 -flto -c a.c -o a.o
gcc -O2 -flto -c b.c -o b.o
gcc -O2 -flto a.o b.o -o app
# Hoac mot lenh:
gcc -O2 -flto a.c b.c -o app
/* Loi ich: mot ham nho trong a.c goi tu b.c gio noi tuyen duoc,
nhu the ca chuong trinh nam trong mot file.
Chi phi:
- lien ket cham hon nhieu, doi khi rat nhieu
- thong bao loi kho doc hon
- can CUNG co -O va -flto o ca hai buoc
Dung LTO cho ban PHAT HANH, khong cho ban phat trien hang ngay.
Do truoc: voi nhieu du an LTO tiet kiem 5-15%, dang gia;
voi mot so du an gan nhu khong doi. */#march=native
gcc -O2 -march=native app.c -o app
/* -march=native cho phep trinh bien dich dung MOI lenh CPU cua
MAY DANG DICH: AVX, AVX2, FMA, va cac phan mo rong khac.
Loi: nhanh hon voi ma so hoc day, vi vector hoa dung lenh rong.
Nguy: binary CHI CHAY tren CPU co cung tap lenh. Dem sang
may cu hon, no bao "Illegal instruction" va sap.
Ba lua chon: */
-march=native /* nhanh nhat, chi chay may nay */
-march=x86-64-v2 /* mot muc co so hop ly, chay hau het may tu 2010 */
-march=x86-64 /* an toan nhat, chay moi may 64 bit */
(khong dat) /* = -march tuong ung -mtune generic, an toan */
/* Quy tac:
- Binary phat hanh cho nguoi khac: DUNG -march=native.
Dung muc co so nhu x86-64-v2, hoac khong dat gi.
- Binary chi chay tren may dich vu ban kiem soat: -march=native
hoac -march ung voi dung dong CPU do.
- Do luong: -march=native khong phai luc nao cung nhanh hon
dang ke. Do truoc khi danh doi tinh di dong. */Tự làm thử
- Đo
cham.cở năm mức trên máy bạn và ghi lại các con số. - So assembly của
tinh()ở-O0và-O2, đếm số lệnh. - Tìm một chương trình mà
-O3chậm hơn hoặc bằng-O2. - Dựng một dự án hai tệp có và không có
-flto, đo chênh lệch. - Dịch với
-Ofastvà tìm một phép tính số thực cho kết quả khác-O2. - So kích thước binary giữa
-O2,-O3, và-Os.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Gỡ lỗi dùng
-O0hoặc-Og; phát hành dùng-O2và đo trước khi lên-O3. - Bước nhảy lớn nhất là O0 tới O2;
-O2nội tuyến hàm nên vòng lặp có thể biến mất khỏi hàm gốc. -O3có thể chậm hơn do phình mã, vector hoá sai chỗ, hoặc áp lực thanh ghi; luôn đo.- LTO tối ưu xuyên tệp, dùng cho bản phát hành, cần cùng phiên bản công cụ ở cả hai bước.
-march=nativenhanh hơn nhưng khoá binary vào một dòng CPU; đừng dùng cho binary phát hành hay trong CI.