ทีมอีมูเลเตอร์ x86 เจอโค้ดที่แย่มากจนแก้ให้ระหว่างอีมูเลชัน
(devblogs.microsoft.com)- อีมูเลเตอร์ x86-32 สร้างโค้ดเนทีฟด้วยการแปลงไบนารีเพื่อรันโค้ด x86-32 บนโปรเซสเซอร์อื่น และให้ประสิทธิภาพดีขึ้นมากเมื่อเทียบกับวิธีแบบอินเทอร์พรีเตอร์
- อีมูเลเตอร์นี้สามารถอธิบายได้ว่า มอง x86-32 เป็นเหมือน ไบต์โค้ด และทำงานเสมือนอีมูเลเตอร์เป็นคอมไพเลอร์ JIT
- โปรแกรมหนึ่งต้องจัดสรรหน่วยความจำประมาณ 64KB บนสแตก และต้องทำการกำหนดค่าเริ่มต้น โดยวิธีทั่วไปคือทำ stack probe ก่อน จากนั้นลด stack pointer แล้วกำหนดค่าเริ่มต้นหน่วยความจำด้วยลูปขนาดเล็ก
- คอมไพเลอร์ของโค้ดนี้กลับสร้าง คำสั่งเขียนไบต์ แยกกัน 65,536 คำสั่งแทนการใช้ลูป โดยแต่ละคำสั่งมีขนาด 4 ไบต์ ทำให้ต้องใช้โค้ด 256KB เพื่อกำหนดค่าเริ่มต้นข้อมูล 64KB
- ทีมอีมูเลเตอร์จึงเพิ่มโค้ดพิเศษในตัวแปลเพื่อจับฟังก์ชันนี้ และแทนที่ด้วย ลูปสั้นๆ ที่ให้ผลลัพธ์เทียบเท่ากัน
เบื้องหลัง: อีมูเลเตอร์ x86-32 และการแปลงไบนารี
- บน Windows เคยมีอีมูเลเตอร์โปรเซสเซอร์ x86-32 สำหรับระบบที่ทำงานบนโปรเซสเซอร์ชนิดอื่นที่ไม่ใช่ x86-32
- ต้นฉบับไม่ได้ระบุชัดว่ากรณีนี้ใช้กับโปรเซสเซอร์ใด
- อีมูเลเตอร์นี้ใช้ การแปลงไบนารี เพื่อสร้างโค้ดเนทีฟที่ทำงานได้เทียบเท่ากับโค้ด x86-32 ต้นฉบับ
- วิธีนี้ให้ประสิทธิภาพดีขึ้นอย่างมากเมื่อเทียบกับการอีมูเลชันแบบอินเทอร์พรีเตอร์
- สามารถทำความเข้าใจได้โดยมองว่า x86-32 เป็นไบต์โค้ด และอีมูเลเตอร์เป็น คอมไพเลอร์ JIT
โค้ดที่เป็นปัญหา: การกำหนดค่าเริ่มต้นหน่วยความจำสแตก 64KB
- โปรแกรมหนึ่งต้องจัดสรรหน่วยความจำประมาณ 64KB บนสแตก และต้องกำหนดค่าเริ่มต้นให้มัน
- วิธีมาตรฐานคือทำ stack probe ก่อน เพื่อตรวจสอบว่าสามารถใช้หน่วยความจำ 64KB ได้หรือไม่
- จากนั้นจึงลบ 65,536 ออกจาก stack pointer แล้วกำหนดค่าเริ่มต้นหน่วยความจำด้วยลูปขนาดเล็กที่กระชับ
การคลี่ลูปเกินเหตุของคอมไพเลอร์
- คอมไพเลอร์ที่คอมไพล์โค้ดนี้ไม่ได้สร้างลูปสำหรับกำหนดค่าเริ่มต้นทีละไบต์
- แต่กลับคลี่ลูปออกเป็น คำสั่ง “เขียนไบต์ลงหน่วยความจำ” แยกกัน 65,536 คำสั่ง
- แต่ละคำสั่งมีความยาว 4 ไบต์
- ผลลัพธ์คือ ต้องใช้ โค้ด 256KB เพื่อกำหนดค่าเริ่มต้นข้อมูล 64KB
การรับมือของทีมอีมูเลเตอร์
- ทีมอีมูเลเตอร์เพิ่มโค้ดพิเศษลงในตัวแปลเพื่อจับฟังก์ชันนี้
- ฟังก์ชันที่ตรวจพบจะถูกแทนที่ด้วย ลูปสั้นๆ ที่ให้พฤติกรรมเทียบเท่ากัน
- การจัดการนี้ไม่ได้แปลโค้ดโปรแกรมต้นฉบับแบบตรงไปตรงมา แต่เป็นการเปลี่ยนแพตเทิร์นโค้ดที่ไม่มีประสิทธิภาพระหว่างการอีมูเลชันให้เป็นรูปแบบที่กระชับกว่า
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
ชอบคอมเมนต์ที่อธิบายเรื่อง loop unrolling ให้ Raymond Chen ฟังอยู่เหมือนกัน
คนที่มาอ่านบทความแบบนี้ไม่ได้รู้พื้นฐานครบทุกอย่างเสมอไป เลยมีหลายคนที่รู้สึกขอบคุณกับเบาะแสที่ช่วยให้ไปเรียนรู้ต่อได้
https://joelonsoftware.com/2004/06/…
คิดว่าน่าจะเป็นบน Alpha เพราะมีงานจำนวนมหาศาลที่ทุ่มลงไปกับ x86 emulator สำหรับแพลตฟอร์มนั้น