Giải thuật 03/09/2026 8 phút

Một hàm ba phép nhân, gcc rút thành đúng một con số nạp sẵn — nếu tôi cho nó biết đầu vào

Ai cũng biết compiler tính sẵn 2+3 thành 5. Tôi đo xem nó đi xa tới đâu: cùng một hàm ba phép nhân, khi đầu vào là hằng 100 thì gcc gấp toàn bộ thành một literal 64-bit (5 lệnh, 0 phép nhân, 0 ns); khi đầu vào từ volatile thì phải tính đủ (22 lệnh, 3 phép nhân, 0,99 ns). Ranh giới nằm ở một câu hỏi.

Giải thuật 03/09/2026 7 phút

"Phải -O3 mới có vector hóa" — niềm tin này đã sai từ gcc 12, và tôi đo để chứng minh

Ai cũng bảo muốn compiler tự vector hóa SIMD thì phải bật -O3. Tô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 không phải đoán theo mức -O, mà là đọc -fopt-info-vec — compiler tự khai.

Giải thuật 03/09/2026 8 phút

Bật -funroll-loops phình mã gấp 2,6 lần mà không nhanh hơn một giây — lợi thật của bung vòng nằm ở chỗ khác

Bung vòng để 'giảm nhánh' là lời giải thích bỏ lỡ điều quan trọng. Tôi đo: bung với nhiều biến tích lũy độc lập nhanh 3,72 lần vì phá chuỗi phụ thuộc, 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. Cái đáng làm không phải chép thân vòng.

Giải thuật 03/09/2026 8 phút

Vì sao bung vòng x8 lại chậm hơn x4? Tôi đếm lệnh và tìm ra thủ phạm: CPU hết thanh ghi

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ời giải cho bí ẩn bung vòng x8 chậm hơn x4 — và vì sao biến cục bộ không hề miễn phí.

Giải thuật 03/09/2026 8 phút

Cùng một hàm, cùng đầu vào, hai kết quả: 1 với -O2 và 1073741824 với một cờ — dấu vân tay của UB

Đọc lại bit của một int như float bằng ép kiểu con trỏ nghe vô hại. Tôi đo: cùng hàm trả về 1 với -O2 nhưng 1073741824 với -fno-strict-aliasing — một cờ đổi kết quả là bằng chứng chắc chắn của hành vi không xác định. Strict aliasing cho tốc độ ~9%, nhưng type-punning ép kiểu vi phạm nó; memcpy mới đúng, mà vẫn nhanh.

Giải thuật 03/09/2026 8 phút

Bật -ffast-math: tổng 20 triệu số chạy nhanh gấp 4 — nhưng ra một con số khác

-ffast-math nghe như nút tăng tốc miễn phí cho code toán. Tôi đo: cùng tổng 20 triệu float, -O3 cho 1999531776 trong 0,571 ns/phần tử, còn -ffast-math cho 1999546496 trong 0,143 ns — nhanh 4 lần nhưng KHÁC kết quả, 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 độ chính xác.