นักพัฒนาถกเถียงเรื่องการแลกเปลี่ยนประสิทธิภาพในเทคนิคการปรับปรุง Lexer ของ Compiler สมัยใหม่

ทีมชุมชน BigGo
นักพัฒนาถกเถียงเรื่องการแลกเปลี่ยนประสิทธิภาพในเทคนิคการปรับปรุง Lexer ของ Compiler สมัยใหม่

ชุมชนโปรแกรมเมอร์กำลังหารือกันอย่างกระตือรือร้นเกี่ยวกับประสิทธิผลในโลกจริงของเทคนิคการปรับปรุง lexer ขั้นสูง หลังจากได้รับแรงบันดาลใจจากบทความล่าสุดเรื่องการสร้าง compiler ที่เร็วเป็นพิเศษ แม้ว่าแนวทางที่ซับซ้อนอย่างการใช้ jump table แบบ hand-rolled และการจัดการหน่วยความจำแบบกำหนดเองจะแสดงให้เห็นความเป็นไปได้ในทางทฤษฎี แต่นักพัฒนากำลังตั้งคำถามว่าการปรับปรุงเหล่านี้จะให้ผลตอบแทนด้านประสิทธิภาพที่มีความหมายในทางปฏิบัติหรือไม่

เทคนิคการปรับปรุงประสิทธิภาพ Lexer หลักที่กล่าวถึง:

  • Jump tables แบบเขียนเองเทียบกับการปรับปรุง switch ที่ compiler สร้างขึ้น
  • การจัดสรรหน่วยความจำแบบกำหนดเองผ่าน FileLoader interfaces
  • การประมวลผล file stream โดยตรงเทียบกับแนวทางที่ใช้ string-view
  • การจัดเก็บ token string โดยใช้ character arrays ขนาดคงที่ (แนะนำ 64 bytes)
  • Memoization สำหรับการประมวลผลตัวเลข (รายงานการปรับปรุงความเร็ว 64%)
  • Perfect hashing สำหรับการระบุ keyword
  • ความเข้ากันได้ข้ามภาษา ( C , C++ , Rust , Go )

การปรับปรุง Compiler สมัยใหม่อาจทำให้เทคนิคแบบ Manual ล้าสมัย

ประเด็นหลักที่เป็นที่ถกเถียงคือการใช้งาน jump table แบบ manual จะมีประสิทธิภาพเหนือกว่าการปรับปรุงของ compiler สมัยใหม่จริงหรือไม่ นักพัฒนาบางคนตั้งคำถามว่า compiler ร่วมสมัยสร้าง jump table ที่มีประสิทธิภาพสำหรับ switch statement ขนาดใหญ่อยู่แล้วหรือไม่ ซึ่งอาจทำให้ทางเลือกที่เขียนด้วยมือไม่จำเป็น การถกเถียงนี้เน้นย้ำถึงความตึงเครียดที่ดำเนินต่อไประหว่างเทคนิคการปรับปรุงระดับต่ำแบบดั้งเดิมกับการไว้วางใจความฉลาดของ compiler สมัยใหม่

การหารือเผยให้เห็นประสบการณ์ที่แตกต่างกันในสถาปัตยกรรมไมโครต่างๆ และกรณีการใช้งานเฉพาะ ในขณะที่นักพัฒนาบางคนรายงานการปรับปรุงที่วัดได้จากการปรับปรุงแบบ manual คนอื่นๆ แนะนำว่าประโยชน์อาจไม่สม่ำเสมอขึ้นอยู่กับแพลตฟอร์มเป้าหมายและโครงสร้างโปรแกรม

ข้อพิจารณาด้านประสิทธิภาพ:

  • การปรับแต่งคอมไพเลอร์สมัยใหม่อาจสร้าง jump table ที่มีประสิทธิภาพโดยอัตโนมัติ
  • การเพิ่มประสิทธิภาพแตกต่างกันอย่างมีนัยสำคัญในสถาปัตยกรรมไมโครต่างๆ
  • การทดสอบประสิทธิภาพมีความจำเป็นเนื่องจากผลลัพธ์ที่ไม่สม่ำเสมอในแพลตฟอร์มต่างๆ
  • การเปลี่ยนเส้นทางการจัดสรรหน่วยความจำสามารถให้ความเร็วที่เพิ่มขึ้นอย่างมาก
  • การเปรียบเทียบคีย์เวิร์ดโดยใช้จำนวนเต็ม 64 บิตสำหรับคีย์เวิร์ดที่มีขนาด ≤8 ไบต์
  • การประมวลผลแบบ stream แลกเปลี่ยนประสิทธิภาพเพื่อความยืดหยุน

แนวทางทางเลือกได้รับความนิยมในหมู่ผู้ปฏิบัติงาน

นักพัฒนาที่มีประสบการณ์กำลังแบ่งปันกลยุทธ์ทางเลือกที่ให้ความสำคัญกับความเรียบง่ายและความสามารถในการบำรุงรักษามากกว่าการปรับปรุงที่ซับซ้อน แนวทางหนึ่งเกี่ยวข้องกับการทำงานโดยตรงกับ file stream แทนที่จะเป็น string view ซึ่งอาจเสียสละประสิทธิภาพบางส่วนแต่เปิดใช้งานความสามารถในการประมวลผล stream วิธีการนี้สามารถปรับให้เข้ากับประเภทอินพุตต่างๆ ได้โดยใช้ฟังก์ชันไลบรารีมาตรฐาน

เทคนิคปฏิบัติอีกอย่างหนึ่งเกี่ยวข้องกับการจัดเก็บ token string โดยตรงภายในโครงสร้าง token โดยใช้ character array ขนาดคงที่ แนวทางนี้ลดการตัดสินใจในระหว่างกระบวนการ lexing โดยมุ่งเน้นไปที่การจำแนกอักขระพื้นฐานแทนที่จะเป็นการจัดการสถานะที่ซับซ้อน

ผมพยายามลดการตัดสินใจในระหว่างการ lexing จริงๆ แล้ว ณ จุดที่ใช้งาน เราสนใจเฉพาะสิ่งที่น้อยมากเท่านั้น: lexeme เริ่มต้นด้วยตัวอักษรหรือตัวเลขหรือไม่?; เป็น whitespace หรือไม่ และ whitespace นั้นเป็นบรรทัดใหม่หรือไม่?; หรือดูเหมือน operator หรือไม่?

ผลการ Benchmark แสดงให้เห็นการเพิ่มประสิทธิภาพที่หลากหลาย

ชุมชนแสดงความสนใจเป็นพิเศษในการวัดประสิทธิภาพที่เป็นรูปธรรม โดยเฉพาะอย่างยิ่งเกี่ยวกับเทคนิคการปรับปรุงหน่วยความจำ นักพัฒนาบางคนแสดงความประหลาดใจต่อการเพิ่มความเร็วที่รายงานจาก memoization ในการประมวลผลตัวเลข แม้ว่าจะยังมีคำถามเกี่ยวกับความสม่ำเสมอของผลลัพธ์เหล่านี้ในสถานการณ์ต่างๆ

การเน้นย้ำเรื่อง benchmarking สะท้อนถึงแนวโน้มที่กว้างขึ้นไปสู่การตัดสินใจปรับปรุงที่อิงหลักฐานแทนที่จะพึ่งพาการปรับปรุงประสิทธิภาพเชิงทฤษฎีเพียงอย่างเดียว แนวทางนี้ช่วยให้นักพัฒนาแยกแยะระหว่างการปรับปรุงที่ให้ประโยชน์จริงกับที่เพิ่มความซับซ้อนโดยไม่มีผลตอบแทนที่มีความหมาย

การใช้งานข้ามภาษาขยายขอบเขตการปรับปรุง

แม้จะมุ่งเน้นไปที่การใช้งาน C และ C++ แต่กลยุทธ์การปรับปรุงเหล่านี้หลายอย่างสามารถแปลไปใช้กับภาษาโปรแกรมอื่นๆ ได้อย่างมีประสิทธิภาพ เทคนิคอย่าง jump table, memory mapping, deferred parsing และ string interning ทำงานได้ในภาษาต่างๆ รวมถึง Rust และ Go

ความเข้ากันได้ข้ามภาษานี้ทำให้การหารือเรื่องการปรับปรุงเกี่ยวข้องกับผู้ชมนักพัฒนาที่กว้างขึ้น แม้ว่ารายละเอียดการใช้งานเฉพาะและลักษณะประสิทธิภาพอาจแตกต่างกันระหว่างภาษาและสภาพแวดล้อม runtime ของแต่ละภาษา

อ้างอิง: Strategies for Very Fast Compilers