- Triển khai một hàm xác định năm nhuận chỉ với 3 lệnh CPU
- Phương pháp này hoạt động không cần nhánh truyền thống, sử dụng phép toán bit và phép nhân
- Cách làm này chính xác trong phạm vi từ năm 0 đến 102499
- Kết quả benchmark cho thấy hiệu năng nhanh hơn khoảng 3,8 lần so với phương pháp hiện có
Tổng quan và vấn đề đặt ra
- Với các năm từ 0 đến 102499, có một hàm kiểm tra năm nhuận chỉ dùng đúng 3 lệnh CPU
- Hàm thực tế được dùng có dạng
((y * 1073750999) & 3221352463) <= 126976
- Bài viết giải thích nguyên lý, cách hoạt động và tính hữu ích thực tế của kỹ thuật bit-twiddling này
Cách kiểm tra năm nhuận truyền thống
- Thông thường, việc xác định năm nhuận được triển khai bằng phép chia lấy dư (modulo) và rẽ nhánh điều kiện
Tối ưu hóa cách tiếp cận chuẩn
- Có thể thay phép kiểm tra
(y % 100) bằng (y % 25), và (y % 400) bằng (y % 16)
- Lý do là trước đó đã chia theo 4, nên có thể chuyển thành quan hệ nhân với 25 và 16
- Có một hằng số ma thuật cho phép biến phép toán
(y % 25) thành phép nhân và so sánh mà không cần chia
- Ví dụ: có thể chuyển thành
x * 3264175145 > 171798691
- Có thể thêm bitmask để thay thế phép chia cho 4 hoặc 16 bằng
(y & 3) hay (y & 15)
- Trình biên dịch có thể tự động thực hiện các biến đổi này, nhưng cũng có thể áp dụng trực tiếp trong các ngôn ngữ khác
Cách triển khai không nhánh (branchless)
Tìm hằng số ma thuật: cách tiếp cận bit-twiddling
- Để làm công thức kiểm tra năm nhuận đơn giản hơn nữa, tác giả sử dụng tìm kiếm tổ hợp và Z3, một SMT Solver
- Dạng công thức:
((y * f) & m) <= t
- Dùng Z3 để tìm các hằng số f, m, t thỏa mãn yêu cầu
- Tìm ra các giá trị cho kết quả chính xác trong phạm vi 0~102499
- Kết quả cuối cùng là
(y * 1073750999) & 3221352463 <= 126976
Giải thích nguyên lý hàm và cấu trúc bên trong
- Phân tích các hằng số ở dạng nhị phân và chia chúng thành ba vùng bit chính: A, B, C
- Trạng thái bit của từng vùng bao quát 3 điều kiện cần để xác định năm nhuận
- Phân rã logic của hàm:
- Vùng A: kiểm tra điều kiện bội số của 4, bao gồm cả việc
(y % 4) != 0
- Vùng B: lọc điều kiện
(y % 100) != 0 bằng nhiều mẫu khác nhau (ví dụ: hai chữ số cuối là 14, 57, 71, v.v.)
- Vùng C: kiểm tra
(y % 16) == 0, tức là bội số của 16
- Giải thích cách phép nhân thực sự kết hợp nhiều điều kiện mà không cần tính số dư
- Khi nhân với hằng số ma thuật, các bội số của 100 tạo ra các mẫu bit đặc trưng
- Ngoài ra còn có phân tích cấu trúc toán học bên trong, như sai số nhân và các mẫu chữ số nhiều hàng
Khả năng mở rộng phạm vi và độ rộng bit
- Khi mở rộng sang 64 bit, bài viết cũng đưa ra cách tìm tổ hợp hằng số ma thuật phù hợp
- Có thể thử nhiều giá trị
f, m, t khác nhau để tìm phạm vi dài nhất
- Trên StackExchange cũng có các ví dụ chứng minh việc dùng Z3 và tổ hợp tối ưu
Benchmark và so sánh hiệu năng thực tế
- Kết quả benchmark:
- Với các giá trị dễ đoán như năm 2025, chênh lệch hầu như không đáng kể ở mức 0,65~0,69ns
- Với đầu vào ngẫu nhiên,
is_leap_year_fast cho hiệu năng nhanh hơn khoảng 3,8 lần
- Tùy theo mẫu đầu vào, cách dùng nhánh có thể khó dự đoán và khi đó phương pháp này có lợi thế đáng kể
Kết luận và khả năng ứng dụng thực tế
- Trong ứng dụng thực tế, nếu giá trị có thể dự đoán thì lợi ích không nhiều, nhưng rất hữu ích trong các tình huống có lượng lớn dữ liệu ngẫu nhiên
- Nếu muốn thay thế thư viện chuẩn trong Python hay C#, cần có benchmark toàn chương trình mang tính thực tế
- Bản thân ý tưởng và cách triển khai rất thú vị, và trong một số tình huống là một giải pháp hấp dẫn về mặt hiệu năng
2 bình luận
Bài viết khiến tôi nhớ đến fast inverse sqrt
Ý kiến Hacker News
is_leap_year1thành dạng nhưis_leap_year2, nên có cảm giác không thật sự cần tự tay tối ưu ở cấp mã nguồn C, dù điều này vẫn có thể hữu ích trong các ngôn ngữ khác, đặc biệt là cách phiên bản mới nhất của mã nguồncalxử lý việc kiểm tra năm nhuận cực kỳ ngắn gọn rất ấn tượng((y * 1073750999) & 3221352463) <= 126976hoạt động lại phức tạp đến vậy, thậm chí điều đó là hiển nhiênCMP(X, Y)hiệu quả cho kiểu so sánh phong cách UNIX, ví dụ tối ưu hàmsignum, ví dụ mã assembly cho Motorola 68000, và bộ macro bit có nguồn gốc từ OpenSolaris, đồng thời cũng tiếc là blog Open Solaris đã biến mất; rất đáng xem với những ai quan tâm đến tối ưu mã