เรื่องราวของวิศวกรซอฟต์แวร์ที่การปรับปรุงประสิทธิภาพผิดพลาดได้จุดประกายการอพยพอย่างกว้างขวางเกี่ยวกับความสำคัญของข้อมูลเบนช์มาร์กที่สมจริงในการทดสอบประสิทธิภาพ นักพัฒนาใช้เวลาสองสัปดาห์สร้างการใช้งาน assembly ที่ปรับปรุงด้วยมือซึ่งให้ผลลัพธ์ที่น่าประทับใจ 4 เท่าในการทดสอบ แต่กลับพบว่าไม่ได้ประโยชน์เลยในการใช้งานจริงเนื่องจากข้อสมมติฐานของเบนช์มาร์กที่ผิดพลาดโดยพื้นฐาน
ข้อผิดพลาดของข้อมูลสุ่มในการทดสอบประสิทธิภาพ
ปัญหาหลักเกิดขึ้นจากการใช้ตัวเลขสุ่มเพื่อทดสอบการปรับปรุง Varint encoding แม้ว่าวิธีการนี้จะดูเหมือนสมเหตุสมผลสำหรับการทดสอบที่ครอบคลุม แต่กลับสร้างสถานการณ์ที่ไม่สมจริงอย่างสิ้นเชิง ตัวเลขสุ่ม 64 บิตมีความเอียงทางคณิตศาสตร์ไปทางค่าที่ใหญ่มาก โดยมี 50% ที่ต้องใช้ไบต์สูงสุด 10 ไบต์ในการเข้ารหัสและเกือบทั้งหมดที่เหลือต้องการ 8-9 ไบต์ ซึ่งหมายความว่าเบนช์มาร์กกำลังทดสอบสถานการณ์ประสิทธิภาพที่แย่ที่สุดของอัลกอริทึมมากกว่าการใช้งานแบบทั่วไป
ชุมชนได้เน้นย้ำว่าสิ่งนี้แสดงถึงความท้าทายที่กว้างขึ้นในงานปรับปรุงประสิทธิภาพ การสร้างสถานการณ์ทดสอบที่เป็นตัวแทนและการนำไปใช้อย่างถูกต้องใน microbenchmarks เป็นงานที่ยากมาก และความล้มเหลวในด้านนี้อาจยากต่อการตรวจพบจนกว่าจะลงทุนเวลาไปอย่างมาก
Varint (Variable-length integer): วิธีการเข้ารหัสแบบกะทัดรัดที่ใช้ 1-10 ไบต์ในการแสดงจำนวนเต็ม โดยตัวเลขที่เล็กกว่าจะใช้ไบต์น้อยกว่า
การกระจายขนาด ขนาด ของ Varint Endoding สำหรับตัวเลข 64-bit แบบสุ่มใส:
- 50% ต้องการ 10 ไบต์ (สูงสุด)
- ~49.6% ต้องการ 9 ไบต์
- ~0.38% ต้องการ 8 ไบต์
- ~0.003% ต้องการ 7 ไบต์
- ~0.00000000000009% ต้องการ 2 ไบต์
- ~0.0000000000000006% ต้องการ 1 ไบต์
การกระจายข้อมูลในโลกจริงเล่าเรื่องที่แตกต่าง
การเปิดเผยเกิดขึ้นเมื่อตรวจสอบข้อมูลการใช้งานจริง ซึ่งเผยให้เห็นว่าตัวเลขในโลกจริงมีแนวโน้มที่จะเล็กกว่าข้อมูลทดสอบแบบสุ่มอย่างมาก เมื่อมองไปรอบๆ ตัวเลขในชีวิตประจำวัน เช่น จำนวนหน้า หมายเลขอ้างอิง รหัสผลิตภัณฑ์ ส่วนใหญ่พอดีกับเพียงสองไบต์ ความแตกต่างพื้นฐานนี้อธิบายว่าทำไม Varint encoding จึงมีอยู่ตั้งแต่แรก เพื่อจัดการกับตัวเลขเล็กๆ ที่ครอบงำแอปพลิเคชันจริงอย่างมีประสิทธิภาพ
ปรากฏการณ์นี้เชื่อมโยงกับหลักการทางคณิตศาสตร์ที่กว้างขึ้นเช่น Benford's Law ซึ่งสังเกตว่าในชุดข้อมูลโลกจริงจำนวนมาก หลักนำที่เล็กกว่าจะปรากฏบ่อยกว่าหลักที่ใหญ่กว่า ความไม่ตรงกันระหว่างความสุ่มทางคณิตศาสตร์และการกระจายข้อมูลในทางปฏิบัติแสดงถึงกับดักทั่วไปสำหรับนักพัฒนาที่ปรับปรุงอัลกอริทึม
รูปแบบการเข้ารหัส Varint:
- ตัวเลขที่น้อยกว่า 128:
1nnnnnnn
(1 ไบต์) - ตัวเลขที่น้อยกว่า 16,384:
1nnnnnnn 0nnnnnnn
(2 ไบต์) - ตัวเลขที่น้อยกว่า 2,097,152:
1nnnnnnn 1nnnnnnn 0nnnnnnn
(3 ไบต์) - จำนวนเต็ม 32 บิต: ต้องใช้ 1-5 ไบต์
- จำนวนเต็ม 64 บิต: ต้องใช้ 1-10 ไบต์
- ใช้ใน: Google Protobuf , Apache Thrift , WASM , DWARF
ความท้าทายของการทำเบนช์มาร์กที่เป็นตัวแทน
การอภิปรายได้เผยให้เห็นว่าแม้แต่นักพัฒนาที่มีประสบการณ์ยังต่อสู้กับการสร้างการทดสอบประสิทธิภาพที่มีความหมาย องค์กรบางแห่งได้สำรวจการใช้โปรไฟล์ข้อมูลการใช้งานจริงหรือการใช้ตัวกรองกับข้อมูลผู้ใช้จริงเพื่อการทำเบนช์มาร์กที่แม่นยำยิ่งขึ้น แม้ว่าวิธีการเหล่านี้จะเผชิญกับความท้าทายในทางปฏิบัติของตัวเอง
การระบุสถานการณ์การใช้งานที่เป็นตัวแทนเพื่อปรับปรุงและการนำสถานการณ์นั้นไปใช้ในไดรเวอร์ทดสอบ microbenchmark เป็นเรื่องที่ยากมากที่จะทำให้ถูกต้อง
เรื่องราวนี้เป็นเครื่องเตือนใจว่าตัวเลขเบนช์มาร์กที่น่าประทับใจไม่มีความหมายอะไรหากไม่มีเงื่อนไขการทดสอบที่สมจริง แม้ว่าความสำเร็จทางเทคนิคของนักพัฒนาในการสร้างอัลกอริทึมที่เร็วขึ้น 4 เท่าจะเป็นเรื่องจริง แต่การปรับปรุงนั้นแก้ปัญหาที่ไม่มีอยู่ในทางปฏิบัติ ประสบการณ์นี้ในที่สุดก็กลายเป็นสิ่งที่มีค่าในฐานะ proof-of-concept สำหรับการปรับปรุง JIT แบบกำหนดเอง แม้ว่าการใช้งานเฉพาะจะถูกยกเลิก
กรณีนี้เน้นย้ำว่าทำไมการปรับปรุงประสิทธิภาพที่ประสบความสำเร็จจึงต้องให้ความสำคัญกับวิธีการวัดมากพอๆ กับการปรับปรุงโค้ดจริง หากไม่มีข้อมูลที่เป็นตัวแทน แม้แต่การปรับปรุงที่ซับซ้อนที่สุดก็สามารถกลายเป็นการแก้ปัญหาที่ไม่มีอยู่อย่างซับซ้อน
อ้างอิง: That time I wasted weeks hand optimizing assembly because I benchmarked on random data