Trình biên dịch và tối ưu hóa: đo thật
Sê-ri đo đạc: mổ xẻ các tối ưu của trình biên dịch bằng thực nghiệm trong container gcc dùng một lần — đo mã máy sinh ra và tốc độ thật, so kỳ vọng với thực tế.
11/45 phần đã đăng
Giải thuật
1
Mức tối ưu -O0 đến -O3
Cùng một code C, -O0 chậm 4,6 lần -O2 vì giữ mọi biến trên ngăn xếp (72 lệnh load/store so với 26 khi lên -O1). Cú nhảy tốc độ lớn nhất nằm ở -O0 đến -O1; còn -O2 và -O3 cho mã gần như y hệt. Đo thật.
03/09/2026
· 7 phút đọc
2
Nội tuyến hàm (inlining)
Với hàm nhỏ, bỏ lệnh call chỉ tiết kiệm 0,15 ns; lợi thật của inline là tối ưu xuyên biên — gọi g(x,0) hằng số, inline giúp compiler gấp hằng và khử nhánh chết, nhanh 1,8 lần. Nhưng inline hàm lớn vào 24 chỗ làm mã phình gấp đôi, ép cache lệnh. Đo thật.
03/09/2026
· 7 phút đọc
3
Khử mã chết
Một vòng 2 tỉ phép tính mà kết quả không được dùng đo ra đúng 0,00 ns — gcc -O2 xóa sạch cả vòng; thêm volatile sink là nhảy lên 1,37 ns thật. DCE còn xóa dead store và mã sau return. Đây là nền tảng vì sao mọi phép đo cần sink. Đo thật.
03/09/2026
· 7 phút đọc
4
Gấp hằng số và lan truyền hằng
Cùng một hàm phức tạp với 3 phép nhân: khi đầu vào là hằng số 100, gcc gấp nó thành một literal 64-bit (5 lệnh, 0 phép nhân, 0 ns); khi đầu vào đến từ volatile thì phải tính đủ (22 lệnh, 3 phép nhân, 0,99 ns). Tính toán dời sang lúc biên dịch. Đo thật.
03/09/2026
· 7 phút đọc
5
Vector hóa tự động
Niềm tin 'phải -O3 mới có vector hóa' đã lỗi thời: đo trên gcc 13, -O2 đã sinh 26 lệnh NEON và nhanh gấp 2 lần -O1 scalar, còn -O3 cho mã y hệt. Và cách biết chắc vòng nào được vector hóa là đọc -fopt-info-vec, không phải đoán theo mức -O. Đo thật.
03/09/2026
· 7 phút đọc
6
Bung vòng lặp
Lợi thật của bung vòng không phải giảm nhánh mà là phá chuỗi phụ thuộc: bung với nhiều biến tích lũy độc lập cho ILP, nhanh 3,72 lần; nhưng bung cơ học của -funroll-loops phình mã 121→314 lệnh mà 0 tăng tốc, và bung x8 còn chậm hơn x4. Đo thật.
03/09/2026
· 7 phút đọc
7
Cấp phát thanh ghi và tràn stack
CPU chỉ có ~28 thanh ghi số nguyên dùng được trên AArch64; vượt ngưỡng đó, biến sống cùng lúc bị tràn ra ngăn xếp, và lệnh load/store trong vòng nóng leo từ 1 (4 biến) lên 103 (48 biến). Đây chính là lý do bung vòng x8 chậm hơn x4. Đo thật.
03/09/2026
· 7 phút đọc
8
Strict aliasing
Cùng một hàm C, cùng đầu vào: đọc lại một biến qua con trỏ ép kiểu trả về 1 với -O2 nhưng 1073741824 với -fno-strict-aliasing — một cờ đổi kết quả là dấu hiệu của hành vi không xác định. Strict aliasing cho tốc độ (~9%) nhưng type-punning ép kiểu là UB. Đo thật.
03/09/2026
· 7 phút đọc
9
-ffast-math
Cùng tổng 20 triệu float: -O3 cho 1999531776 trong 0,571 ns/phần tử; -O3 -ffast-math cho 1999546496 trong 0,143 ns (nhanh 4 lần) — khác nhau vì nó sắp lại phép cộng để vector hóa, mà số thực máy không kết hợp. Tốc độ đổi lấy đúng IEEE. Đo thật.
03/09/2026
· 7 phút đọc
10
UB mở khóa tối ưu
Tưởng hành vi không xác định (UB) chỉ là 'crash hoặc giá trị rác'. Đo ra nó là lời hứa compiler tin bạn giữ, rồi xóa chính kiểm tra an toàn bạn viết: if(a+100<a) và if(p==NULL) biến mất ở -O2. Đo thật.
03/09/2026
· 9 phút đọc
11
LTO xuyên file
Tưởng -O2 đã tối ưu hết mức, thêm -flto chỉ chậm build vô ích. Đo ra -O2 tối ưu theo từng file, ranh giới file chặn nội tuyến; -flto phá ranh giới đó, hàm hot cross-file được nội tuyến — nhanh 1,59 lần. Đo thật.
03/09/2026
· 7 phút đọc
Còn 34 phần nữa sẽ lần lượt được đăng.