- มีการสร้างฟังก์ชันสำหรับตรวจสอบปีอธิกสุรทินโดยใช้ คำสั่ง CPU เพียง 3 คำสั่ง
- วิธีนี้ทำงานแบบไม่มีการแตกแขนงโดยอาศัยการดำเนินการบิตและการคูณ
- วิธีนี้ ให้ผลถูกต้องในช่วงปี 0~102499
- ผลการเบนช์มาร์กแสดงว่าให้ ประสิทธิภาพเร็วขึ้นราว 3.8 เท่า เมื่อเทียบกับวิธีเดิม
ภาพรวมและการตั้งปัญหา
- สำหรับปีในช่วง 0 ถึง 102499 มีฟังก์ชันที่ใช้ คำสั่ง CPU เพียง 3 คำสั่งในการตรวจสอบปีอธิกสุรทิน
- ฟังก์ชันที่ใช้จริงมีโครงสร้างเป็น
((y * 1073750999) & 3221352463) <= 126976
- อธิบายหลักการ การทำงาน และประโยชน์เชิงปฏิบัติของเทคนิค bit-twiddling นี้
วิธีตรวจสอบปีอธิกสุรทินแบบดั้งเดิม
- โดยทั่วไปการตรวจสอบปีอธิกสุรทินจะ ทำด้วยการหารเอาเศษ (modulo) และการแตกแขนงตามเงื่อนไข
การปรับแต่งแนวทางมาตรฐานให้เหมาะสมขึ้น
- สามารถแทนการตรวจ
(y % 100) ด้วย (y % 25) และแทน (y % 400) ด้วย (y % 16) ได้
- เหตุผลคือก่อนหน้านั้นมีการหารด้วย 4 ไปแล้ว จึงเปลี่ยนเป็นความสัมพันธ์ของการคูณ 25 และ 16 ได้
- มีค่าคงที่มหัศจรรย์ที่ใช้แปลงการคำนวณ
(y % 25) ให้เป็น การคูณและการเปรียบเทียบโดยไม่ต้องหาร
- ตัวอย่าง: แปลงเป็น
x * 3264175145 > 171798691 ได้
- สามารถใช้บิตมาสก์เพิ่มเข้ามาเพื่อแทนการหารด้วย 4 หรือ 16 ผ่าน
(y & 3) หรือ (y & 15)
- คอมไพเลอร์สามารถทำการแปลงเหล่านี้โดยอัตโนมัติได้ แต่ก็สามารถนำไปใช้เองในภาษาอื่นได้เช่นกัน
วิธีทำแบบไม่มีการแตกแขนง (branchless)
การหาค่าคงที่มหัศจรรย์: แนวทางแบบ bit-twiddling
- เพื่อทำให้สูตรตรวจปีอธิกสุรทิน เรียบง่ายยิ่งขึ้น จึงใช้การค้นหาเชิงจัดหมู่ร่วมกับ Z3 ซึ่งเป็น SMT Solver
- รูปแบบคือ
((y * f) & m) <= t
- ใช้ Z3 ค้นหาค่าคงที่ f, m, t ที่ตอบโจทย์
- เพื่อให้ได้ผลลัพธ์ที่ถูกต้องในช่วง 0~102499
- ผลลัพธ์สุดท้ายคือ
(y * 1073750999) & 3221352463 <= 126976
อธิบายหลักการของฟังก์ชันและโครงสร้างภายใน
- วิเคราะห์ค่าคงที่ในรูปเลขฐานสอง และแบ่งออกเป็น 3 บริเวณบิตหลักคือ A, B, C
- สถานะบิตของแต่ละบริเวณครอบคลุมเงื่อนไขการตรวจปีอธิกสุรทินทั้ง 3 ข้อ
- การแยกองค์ประกอบเชิงตรรกะของฟังก์ชัน:
- บริเวณ A: ตรวจเงื่อนไขการเป็นพหุคูณของ 4 รวมถึงกรณี
(y % 4) != 0
- บริเวณ B: กรองกรณี
(y % 100) != 0 ด้วยแพตเทิร์นหลากหลายแบบ (เช่น ลงท้ายด้วย 14, 57, 71 เป็นต้น)
- บริเวณ C: ตรวจ
(y % 16) == 0 หรือก็คือการเป็นพหุคูณของ 16
- อธิบายหลักการที่การคูณสามารถ รวมหลายเงื่อนไขเข้าด้วยกันได้โดยไม่ต้องคำนวณเศษโดยตรง
- เมื่อคูณด้วยค่าคงที่มหัศจรรย์ จะเกิดแพตเทิร์นบิตเฉพาะสำหรับกรณีอย่างพหุคูณของ 100
- นอกจากนี้ยังมีการวิเคราะห์โครงสร้างทางคณิตศาสตร์ภายใน เช่น ความคลาดเคลื่อนจากการคูณและแพตเทิร์นของตัวเลขหลายหลัก
ความเป็นไปได้ในการขยายช่วงและความกว้างบิต
- หากขยายเป็น 64 บิต ก็มีการเสนอวิธีค้นหาชุดค่าคงที่มหัศจรรย์ที่เหมาะสมเช่นกัน
- สามารถลองปรับค่า
f, m, t ได้หลายแบบเพื่อหาช่วงที่รองรับได้ยาวที่สุด
- บน StackExchange ก็มีตัวอย่างการพิสูจน์ชุดค่าที่เหมาะสมและการใช้ Z3 เช่นกัน
เบนช์มาร์กและการเปรียบเทียบประสิทธิภาพจริง
- ผลเบนช์มาร์ก:
- สำหรับค่าที่คาดเดาได้ เช่น ปี 2025 ความต่างแทบไม่มี โดยอยู่ที่ 0.65~0.69ns
- เมื่ออินพุตเป็นแบบสุ่ม
is_leap_year_fast ให้ ประสิทธิภาพเร็วขึ้นราว 3.8 เท่า
- หากแพตเทิร์นอินพุตทำให้การแตกแขนง (branching) คาดเดาได้ยาก วิธีนี้จะมีข้อได้เปรียบอย่างมาก
บทสรุปและความเหมาะสมในการใช้งานจริง
- ในงานจริง หากค่ามีรูปแบบที่คาดเดาได้ ประโยชน์อาจไม่มากนัก แต่ในสถานการณ์ที่มี ข้อมูลสุ่มจำนวนมาก จะมีประโยชน์อย่างยิ่ง
- หากจะนำไปแทนที่ไลบรารีมาตรฐานจริงใน Python หรือ C# ก็ยังจำเป็นต้องมี การเบนช์มาร์กระดับทั้งโปรแกรมในสภาพจริง
- ทั้งแนวคิดและวิธีการนำไปใช้มีความน่าสนใจ และในบางสถานการณ์ก็เป็นโซลูชันที่น่าดึงดูดในด้านประสิทธิภาพ
2 ความคิดเห็น
บทความที่ชวนให้นึกถึง Fast inverse sqrt
ความคิดเห็นจาก Hacker News
is_leap_year1ให้เป็นแบบis_leap_year2ได้อัตโนมัติ เลยคิดว่าในระดับซอร์ส C อาจไม่จำเป็นต้องมานั่งปรับแต่งเองมากนัก แต่กับภาษาอื่นก็อาจยังมีประโยชน์อยู่ โดยเฉพาะอย่างยิ่งประทับใจกับการที่ซอร์สโค้ดของcalเวอร์ชันล่าสุดจัดการการตรวจปีอธิกสุรทินได้อย่างเรียบง่ายมาก((y * 1073750999) & 3221352463) <= 126976ทำงานอย่างไรจะต้องซับซ้อน เพราะมันก็เป็นเรื่องธรรมดาอยู่แล้วCMP(X, Y)ที่มีประสิทธิภาพสำหรับการเปรียบเทียบสไตล์ UNIX, ตัวอย่างการปรับแต่งฟังก์ชัน signum, ลิงก์ตัวอย่างโค้ดแอสเซมบลีสำหรับ Motorola 68000, ชุด bit macro ที่มาจาก OpenSolaris และเทคนิคการปรับแต่งอีกหลากหลายแบบ พร้อมบ่นเสียดายว่าบล็อก Open Solaris หายไปแล้ว และแนะนำสำหรับคนที่สนใจการปรับแต่งโค้ด