นักพัฒนาเสียเวลาหลายสัปดาห์ปรับปรุงประสิทธิภาพเพิ่ม 4 เท่า แต่ไม่ได้ผลจริงเพราะข้อมูลเบนช์มาร์กผิดพลาด

ทีมชุมชน BigGo
นักพัฒนาเสียเวลาหลายสัปดาห์ปรับปรุงประสิทธิภาพเพิ่ม 4 เท่า แต่ไม่ได้ผลจริงเพราะข้อมูลเบนช์มาร์กผิดพลาด

เรื่องราวของวิศวกรซอฟต์แวร์ที่การปรับปรุงประสิทธิภาพผิดพลาดได้จุดประกายการอพยพอย่างกว้างขวางเกี่ยวกับความสำคัญของข้อมูลเบนช์มาร์กที่สมจริงในการทดสอบประสิทธิภาพ นักพัฒนาใช้เวลาสองสัปดาห์สร้างการใช้งาน 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