- 1BRC: ชาเลนจ์ให้เขียนโค้ดอ่านค่าการวัดอุณหภูมิจากไฟล์ข้อความที่มี 1 พันล้านแถว แล้วคำนวณอุณหภูมิต่ำสุด/เฉลี่ย/สูงสุดของแต่ละสถานี
- จัดขึ้นระหว่างวันที่ 1 มกราคม 2024 ถึง 31 มกราคม 2024 โดยมีเป้าหมายคือใช้ Java รุ่นล่าสุดให้เกิดประโยชน์สูงสุด
- จากนั้นผู้คนก็เริ่มสนใจและลองทำด้วยภาษาต่าง ๆ (Rust, Go, C++, SQL)
- แนะนำโซลูชัน 9 แบบที่เขียนด้วย Go อย่างละเอียด (เรียงจากช้าไปเร็ว)
ค่าพื้นฐานสำหรับการวัด
- ใช้คำสั่ง
cat เพื่ออ่านข้อมูลข้อความ 1 พันล้านแถว (13GB) ใช้เวลา 1.052 วินาที
- คำสั่ง
wc ที่ประมวลผลไฟล์จริงใช้เวลาเกือบ 1 นาที (55.710 วินาที)
- การแก้ปัญหาด้วยโซลูชัน AWK ใช้เวลา 7 นาที 35 วินาที
โซลูชัน 1: Go แบบเรียบง่ายและเป็นสำนวนมาตรฐาน
- โซลูชันแรกที่ใช้ไลบรารีมาตรฐานของ Go ใช้เวลา 1 นาที 45 วินาที
- อ่านแต่ละบรรทัดด้วย
bufio.Scanner และแยกด้วย strings.Cut โดยใช้ ';' เป็นตัวคั่น
- แปลงค่าอุณหภูมิด้วย
strconv.ParseFloat และสะสมผลลัพธ์ด้วย map ของ Go
โซลูชัน 2: map ที่เก็บค่าพอยน์เตอร์
- ใช้
map[string]*stats เพื่อหลีกเลี่ยงการแฮชสองครั้งใน map
- การใช้ค่าพอยน์เตอร์ช่วยลดเวลาจาก 1 นาที 45 วินาทีเหลือ 1 นาที 31 วินาที
โซลูชัน 3: เลี่ยง strconv.ParseFloat
- ใช้โค้ดที่เขียนเองเพื่อแปลงค่าอุณหภูมิแทน
strconv.ParseFloat
- ลดเวลาจาก 1 นาที 31 วินาทีเหลือ 55.8 วินาที
โซลูชัน 4: ใช้จำนวนเต็มแบบ fixed-point
- แทนค่าอุณหภูมิเป็นจำนวนเต็มเพื่อหลีกเลี่ยงการคำนวณแบบ floating-point
- ลดเวลาจาก 55.8 วินาทีเหลือ 51.0 วินาที
โซลูชัน 5: เลี่ยง bytes.Cut
- แทนที่จะสแกนชื่อสถานีทั้งหมดเพื่อหา ';' ก็เปลี่ยนมาแยกข้อมูลจากท้ายบรรทัด
- ลดเวลาจาก 51.0 วินาทีเหลือ 46.0 วินาที
โซลูชัน 6: เลี่ยง bufio.Scanner
- เอา
bufio.Scanner ออกแล้วอ่านไฟล์เป็นชังก์ขนาดใหญ่
- ลดเวลาจาก 46.0 วินาทีเหลือ 41.3 วินาที
โซลูชัน 7: แฮชเทเบิลแบบกำหนดเอง
- สร้างแฮชเทเบิลขึ้นมาเองแทนการใช้ map ของ Go
- ลดเวลาจาก 41.3 วินาทีเหลือ 25.8 วินาที
โซลูชัน 8: ประมวลผลชังก์แบบขนาน
- ทำให้โค้ดแบบเรียบง่ายและเป็นสำนวนมาตรฐานทำงานแบบขนาน จนลดเวลาจาก 1 นาที 45 วินาทีเหลือ 24.3 วินาที
โซลูชัน 9: รวมทุกการปรับแต่งและการประมวลผลแบบขนาน
- นำทุกเทคนิคการปรับแต่งมารวมกับการประมวลผลแบบขนาน จนลดเวลาจาก 24.3 วินาทีเหลือ 3.99 วินาที
ตารางผลลัพธ์
- มีตารางเปรียบเทียบโซลูชัน Go ทั้งหมด รวมถึงโซลูชัน Go และ Java ที่เร็วที่สุด
- เวอร์ชัน Go ที่เร็วที่สุดประมวลผลได้ใน 2.90 วินาที ส่วนเวอร์ชัน Java ใช้ 0.953 วินาที
- เวอร์ชัน Java ที่ใช้เวลาไม่ถึง 1 วินาทีเป็นผลงานของ Thomas Wuerthinger (ผู้สร้าง GraalVM) ซึ่งน่าจะทำได้เพราะเป็นผู้เชี่ยวชาญด้านนี้โดยตรง
ความเห็นส่งท้าย
- สำหรับงานเขียนโปรแกรมทั่วไป โค้ดที่เรียบง่ายและเป็นสำนวนมาตรฐานคือจุดเริ่มต้นที่ดี
- หากกำลังสร้าง data processing pipeline การทำให้โค้ดเร็วขึ้น 4 เท่าหรือ 26 เท่าสามารถเพิ่มความพึงพอใจของผู้ใช้และช่วยประหยัดต้นทุนคอมพิวต์ได้
- หากกำลังสร้าง runtime หรือ interpreter การเพิ่มประสิทธิภาพเป็นเรื่องสำคัญ
ความเห็นของ GN⁺
- บทความนี้สำรวจวิธีต่าง ๆ ในการปรับแต่งการประมวลผลข้อมูลขนาดใหญ่ด้วยภาษา Go จึงเป็นกรณีศึกษาด้าน performance optimization ที่น่าสนใจ
- ยังแสดงให้เห็นว่าการก้าวข้ามไลบรารีมาตรฐานของ Go ไปใช้โครงสร้างข้อมูลที่สร้างเอง เช่น แฮชเทเบิลแบบกำหนดเอง มีบทบาทสำคัญในกระบวนการปรับแต่ง
- บทความเน้นย้ำประสิทธิผลของการประมวลผลแบบขนาน และการผสานการปรับแต่งบนคอร์เดี่ยวเข้ากับการทำงานแบบขนานเพื่อให้ได้ประสิทธิภาพที่ดีขึ้นอย่างมาก
- บทความนี้ให้มุมมองที่เป็นประโยชน์กับวิศวกรซอฟต์แวร์ที่พัฒนาแอปพลิเคชันที่ไวต่อประสิทธิภาพ
- อย่างไรก็ดี ความมีประโยชน์ของการปรับแต่งระดับนี้ในสภาพแวดล้อมโปรดักชันจริงอาจแตกต่างกันไปตามกรณีใช้งาน ไม่ใช่ทุกแอปพลิเคชันที่จะต้องการการปรับแต่งระดับนี้
6 ความคิดเห็น
อยากรู้เหมือนกันว่าในขั้นตอนที่ 7 มีการทำอะไรเกิดขึ้นแบบเจาะจงบ้าง เป็นช่วงที่ประสิทธิภาพดีขึ้นอย่างมากเลย 555
น่าสนใจที่เขาแยกเป็นแต่ละสเต็ปแล้วแสดงเวลาในการปรับปรุงประสิทธิภาพให้เห็น 555
แค่ใช้
wcก็ใช้เวลาแค่นาทีเดียวเอง.... สุดยอดที่สุดก็คือการไม่ต้องเขียนโค้ดนี่แหละ... ฮ่าๆขอบคุณที่แชร์บทความดีๆ ครับ ทำให้นึกถึงช่วงหนึ่งที่เคยหมกมุ่นกับการปรับแต่งประสิทธิภาพระบบมากๆ เลย 555
พอมีประสบการณ์พัฒนามากขึ้น ก็เจออยู่บ่อยๆ ว่าโค้ดที่ปรับแต่งจนสุดทางนั้นดูแลรักษายาก ทำให้ในสภาพแวดล้อมขององค์กรใช้งานจริงได้ลำบาก เลยค่อยๆ ห่างออกมาจากเส้นทางของการ optimization ไปเองครับ (จู่ๆ ก็กลายเป็นการทบทวนตัวเองส่วนตัวเลย)
โค้ดที่ปรับให้เหมาะกับองค์กร!!
ความคิดเห็นจาก Hacker News
cat,wcเป็นต้น เพื่อหาค่าพื้นฐานจึงน่าสนใจเป็นพิเศษ และมองว่านี่เป็นวิธีง่าย ๆ ในการได้ช่วงค่าที่ "สมเหตุสมผล"unsafe.Pointer, ฟังก์ชันในแพ็กเกจbytesและbitsของไลบรารีมาตรฐานที่เขียนด้วยแอสเซมบลี, การตั้งค่าเพื่อปิด garbage collection และวิธีตรึง goroutine ไว้กับเธรดawk,grepและเครื่องมืออื่น ๆ จะเร็วขึ้นไปอีกขั้น และอ้างว่าเพียงเพิ่มLC_ALL=Cให้โซลูชันawkก็สามารถลดเวลาประมวลผลให้ต่ำกว่า 1 นาทีได้