- โปรเจกต์ CPython เพิ่งนำกลยุทธ์การอิมพลีเมนต์ใหม่สำหรับไบต์โค้ดอินเทอร์พรีเตอร์มาใช้ โดยผลลัพธ์เบื้องต้นแสดงให้เห็นว่าประสิทธิภาพดีขึ้นเฉลี่ย 10-15% บนหลากหลายแพลตฟอร์ม
- อย่างไรก็ตาม การเพิ่มขึ้นของประสิทธิภาพนี้ส่วนใหญ่เป็นผลจากการหลบเลี่ยงปัญหา regression ใน LLVM 19 เมื่อเทียบกับเกณฑ์อ้างอิงที่ดีกว่า (เช่น GCC, clang-18, หรือ LLVM 19 ที่ใช้แฟล็กปรับแต่งเฉพาะ) การเพิ่มขึ้นของประสิทธิภาพลดลงเหลือ 1-5%
ผลลัพธ์ด้านประสิทธิภาพ
- ได้ทำเบนช์มาร์กหลายบิลด์ของ CPython อินเทอร์พรีเตอร์โดยใช้คอมไพเลอร์และตัวเลือกการตั้งค่าหลายแบบ ทดสอบบนเซิร์ฟเวอร์ Intel และ Apple M1 Macbook Air
- ทุกบิลด์ใช้ LTO และ PGO และใช้ค่าเฉลี่ยที่รายงานโดย
pypeformance/pyperf compare_to โดยอ้างอิง clang18 เป็นฐาน
- การเปรียบเทียบประสิทธิภาพของคอมไพเลอร์
- Apple M1 Macbook Air :
- clang18: ค่าฐาน
- clang19: ช้ากว่า 1.12 เท่า
- clang19.taildup: ช้ากว่า 1.02 เท่า
- clang19.tc: ช้ากว่า 1.00 เท่า
- gcc: N/A
- อินเทอร์พรีเตอร์แบบ tail-call ยังแสดงการปรับปรุงความเร็วเมื่อเทียบกับ clang-18 แต่การลดลงของความเร็วจากการย้ายไปใช้ clang-19 นั้นรุนแรงกว่าอย่างชัดเจน
LLVM regression
พื้นหลังแบบย่อ
- ไบต์โค้ดอินเทอร์พรีเตอร์แบบดั้งเดิมประกอบด้วยคำสั่ง
switch ภายในลูป while โดยคอมไพเลอร์ส่วนใหญ่จะคอมไพล์ switch ให้เป็นตารางกระโดด
- คอมไพเลอร์ C สมัยใหม่รองรับแพตเทิร์นที่นำ address ของ label มาใช้เป็น "computed goto" โดย CPython ใช้แพตเทิร์นนี้ก่อนงาน tail-call
LLVM 19 regression
- LLVM 19 เพิ่มข้อจำกัดใน tail-duplication pass โดยจะหยุดการทำซ้ำเมื่อขนาด IR เกินขีดจำกัดหนึ่ง ส่งผลให้ใน CPython การกระโดด dispatch ทั้งหมดถูกรวมเข้าด้วยกัน และทำให้เป้าหมายของอิมพลีเมนเทชันแบบ
goto ที่อิง computed goto สูญเปล่าโดยสิ้นเชิง
ความผิดปกติเพิ่มเติม
- แม้จะมั่นใจว่าการเปลี่ยนแปลงของตรรกะการทำซ้ำสำหรับ tail call เป็นสาเหตุของ regression แต่ก็ยังอธิบาย ขนาด ของ regression ได้ไม่ครบถ้วน
- บนโปรเซสเซอร์สมัยใหม่ การเพิ่มความเร็วราว 2-4% ถือว่าเกิดขึ้นได้ทั่วไปมากกว่า
จำเป็นต้องมี computed goto หรือไม่?
- เบนช์มาร์ก
clang19.nocg อ้างว่าทำงาน เร็วกว่า clang19 ซึ่งแสดงให้เห็นว่าคอมไพเลอร์สามารถทำ optimization แบบเดียวกันได้แม้ใช้อินเทอร์พรีเตอร์ที่อิง switch
การแก้ไข
- LLVM pull request 114990 ได้แก้ regression นี้แล้ว และการแก้ไขดังกล่าวช่วยกู้คืนประสิทธิภาพกลับมาได้ตามที่คาดไว้
ข้อคิดทบทวน
ว่าด้วยการทำเบนช์มาร์ก
- เมื่อต้องปรับแต่งระบบ จะมีการจัดทำทั้งตัวเบนช์มาร์กและวิธีวิทยาการทำเบนช์มาร์ก เพื่อประเมินการเปลี่ยนแปลงที่เสนอ
- เบนช์มาร์กต้องอาศัยสมมติฐานและความเชื่อมากขึ้น เมื่อต้องการนำจุดข้อมูลเฉพาะไปสรุปเป็นภาพรวม
ค่าฐาน
- เมื่อเสนอวิธีแก้ปัญหาหรือแนวทางใหม่ การเปรียบเทียบกับ "แนวทางที่รู้กันว่าดีที่สุดในปัจจุบัน" ถือเป็นเรื่องปกติ
ว่าด้วยวิศวกรรมซอฟต์แวร์
- ระบบซอฟต์แวร์มีความซับซ้อน เชื่อมโยงถึงกัน และเปลี่ยนแปลงอย่างรวดเร็ว
- คอมไพเลอร์สำหรับ optimization ต้องอยู่ในภาวะตึงเครียดระหว่างการเคารพเจตนาของโปรแกรมเมอร์กับการทำโค้ดให้เหมาะสมที่สุด
คอมไพเลอร์สำหรับ optimization
- แอตทริบิวต์
musttail แสดงถึงความสามารถคอมไพเลอร์รูปแบบใหม่ที่เกี่ยวข้องกับ optimization ซึ่งอาจมอบสไตล์ที่ทรงพลังกว่าในการเขียนโค้ดที่ไวต่อประสิทธิภาพ
อีกเรื่องหนึ่งเกี่ยวกับ nix
nix มีประโยชน์มากในโปรเจกต์นี้ และช่วยอย่างมากในการจัดการและบิลด์ Python อินเทอร์พรีเตอร์หลายเวอร์ชัน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
สวัสดีครับ ผมเป็นผู้เขียน PR ที่นำ tail-calling interpreter เข้ามาใน CPython
การทำ benchmark เป็นงานที่ยากมากจริง ๆ
ขอชื่นชมผู้เขียนที่สืบหาความจริงของปัญหานี้จนพบ
นี่เป็นตัวอย่างที่ดีว่า C ไม่ใช่ภาษาที่ "ใกล้เครื่อง" เสมอไป
จากการปรับวิธีที่คอมไพเลอร์จัดโครงสร้างลูป ทำให้ tail-call interpreter ไม่ได้มีประสิทธิภาพอย่างที่ประกาศไว้
การประเมินประสิทธิภาพของ Python build เป็นเรื่องยากมาก
การอภิปรายที่เกี่ยวข้อง:
เป็นบทความที่ยอดเยี่ยม
ไม่นานมานี้ผมทำ benchmark ตั้งแต่ Python 3.9 ถึง 3.13
สงสัยว่าการ optimize แบบนี้เกี่ยวข้องกับ tail-call optimization อย่างไร