Rust 1.98 เปิดทางคำนวณ Float แบบ Algebraic: เร็วขึ้นได้ แต่คำตอบอาจขยับ
งานคำนวณภาพ เสียง สถิติ หรือ simulation มักมีลูปบวกและคูณเลขทศนิยมจำนวนมาก จุดที่น่าสนใจใน Rust 1.98 คือ compiler ได้รับอนุญาตให้จัดกลุ่มงานบางส่วนใหม่ เพื่อเปิดทางให้คำนวณหลายค่าไปพร้อมกันได้ แต่สิทธิ์นั้นไม่ได้มาฟรีครับ คำตอบบิตต่อบิตอาจต่างจากเดิม
Rust 1.98.0 ประกาศเมื่อ 20 สิงหาคม 2026 และเพิ่มเมธอด algebraic_add, algebraic_sub, algebraic_mul, algebraic_div และ algebraic_rem ให้ f32 กับ f64 แบบ stable คำว่า algebraic ในที่นี้แปลว่าเราอนุญาตให้ compiler ใช้สมบัติทางพีชคณิตของจำนวนจริง แม้เลข floating-point ในคอมพิวเตอร์จะทำตามสมบัติเหล่านั้นไม่ครบทุกกรณี
WHY: วงเล็บมีผลกับ Float
ในคณิตศาสตร์ การบวก a + b + c + d จะจัดเป็นคู่ไหนก็ได้ แต่ floating-point มีจำนวนหลักจำกัด การปัดเศษจึงเกิดหลังแต่ละขั้น ถ้ารวมค่าที่ใหญ่มากกับค่าที่เล็กมากก่อน ค่าขนาดเล็กอาจหายไป การเปลี่ยนจาก ((a + b) + c) + d เป็น (a + b) + (c + d) จึงอาจให้บิตสุดท้ายต่างกัน
Rust ปกติรักษาลำดับตาม expression ที่เราเขียน เพื่อให้ผลคาดเดาได้ เมธอด algebraic_* เป็นการบอกอย่างชัดเจนว่า “บริเวณนี้ยอมให้จัดกลุ่มใหม่ได้” compiler จึงอาจแบ่งผลรวมเป็นหลายก้อนหรือ vectorize ลูปได้กว้างขึ้น แนวคิดใกล้กับ fast-math ในภาษาอื่น แต่ Rust ทำเป็นเมธอดราย operation ไม่ใช่สวิตช์ครอบทั้งโปรแกรม
ข้อสำคัญจาก release note คือชุด optimization ที่แน่นอนไม่ได้เป็นสัญญา และเมธอดเหล่านี้จึงให้ผลแบบ non-deterministic ระหว่าง compiler version หรือ target ได้ อย่างไรก็ตาม การใช้เมธอดนี้ไม่ทำให้เกิด undefined behavior
HOW: เลือกขอบเขตให้เล็กและวัดจริง
จุดเริ่มที่ดีไม่ใช่เปลี่ยนเครื่องหมายบวกทุกตัว แต่เลือก kernel เล็กที่กิน CPU จริง เช่น dot product, การรวม pixel หรือการประมวลผลสัญญาณ แล้วเปลี่ยนเฉพาะขั้นบวกและคูณภายใน kernel นั้น
จากนั้นเก็บหลักฐานสามชุด:
- benchmark บน target ที่ deploy จริง เพราะ compiler อาจ vectorize ต่างกันตาม CPU
- error tolerance ของผลลัพธ์ เช่น absolute หรือ relative error แทนการเทียบ
== - golden dataset ที่มีค่าขอบ เช่น
NaN, infinity, ค่าบวกและลบขนาดต่างกันมาก
ถ้า benchmark ไม่ดีขึ้น ให้กลับไปใช้ operator ปกติ เพราะเราไม่ควรยอมเสีย reproducibility โดยไม่ได้ประโยชน์ หากดีขึ้น ให้บันทึก tolerance และเหตุผลไว้ใกล้ function เพื่อไม่ให้คนรุ่นหลังเปลี่ยน test กลับไปเทียบบิตแบบเงียบ ๆ
งานที่ไม่ควรเริ่มด้วย Algebraic Float
ระบบบัญชีไม่ควรใช้ binary float เป็นฐานอยู่แล้ว งานเข้ารหัส ลายเซ็น hash และ serialization ที่ต้องได้ไบต์เหมือนเดิมก็ไม่เหมาะ งานวิทยาศาสตร์บางประเภทต้องทำซ้ำข้ามเครื่องเพื่อยืนยันผล จึงควรให้ reproducibility มาก่อน throughput
อีกกลุ่มคือระบบที่ใช้ NaN หรือ signed zero เป็นสัญญาณทาง domain อย่าคิดว่าเมธอดใหม่เป็นเพียง “บวกแบบเร็ว” ควรอ่าน contract ของ operation และทดสอบค่าพิเศษตามงานจริงเสมอ
เมธอดใหม่เหมาะกับพื้นที่ที่ทีมยอมรับ numerical tolerance อยู่แล้ว และมี profiler ยืนยันว่าคอขวดอยู่ตรง arithmetic loop จริง ไม่ใช่ I/O, allocation หรือการจัดรูปข้อมูล
Recap
Rust 1.98 ไม่ได้เปิด fast-math ทั้งโปรแกรม แต่ให้เราเลือก operation ที่ยอมให้ compiler จัดกลุ่มใหม่ได้ทีละจุด ผลคือมีโอกาส vectorize และเร็วขึ้น แลกกับคำตอบ floating-point ที่อาจขยับตาม optimization
พรุ่งนี้ลองทำสามอย่างครับ: หา hot loop จาก profiler, กำหนด tolerance ก่อนแก้ และ benchmark บน target จริง ถ้าเร็วขึ้นโดย error อยู่ในขอบที่ domain รับได้ ค่อยขยายขอบเขตอย่างมีหลักฐาน