ชุมชนคอมพิวเตอร์เก่ากำลังตื่นเต้นกับการค้นพบเกี่ยวกับการปรับปรุงโค้ด C ให้เหมาะสมสำหรับโปรเซสเซอร์ Motorola 68000 โดยเฉพาะสำหรับฮาร์ดแวร์เกมเก่าอย่าง Neo Geo สิ่งที่เริ่มต้นเป็นเพียงฟังก์ชันการล้างหน้าจออย่างง่าย ๆ ได้เปิดเผยข้อมูลเชิงลึกที่น่าสนใจเกี่ยวกับพฤติกรรมของคอมไพเลอร์และการปรับปรุงเฉพาะฮาร์ดแวร์ที่นักพัฒนาหลายคนไม่เคยรู้มาก่อน
การใช้คำสั่งที่ไม่ชัดเจนอย่างชาญฉลาดของ SNK
หนึ่งในการเปิดเผยที่น่าสนใจที่สุดมาจากวิธีที่ SNK ผู้สร้างเกม Neo Geo ใช้ประโยชน์จากพฤติกรรมที่ไม่ได้บันทึกไว้ในเอกสารของโปรเซสเซอร์ 68000 พวกเขาใช้คำสั่ง SBCD (Subtract Binary Coded Decimal) ที่ไม่ค่อยเห็นสำหรับตัวจับเวลาในเกม โดยใช้ประโยชน์จาก overflow flag ที่ถูกระบุอย่างเป็นทางการว่าไม่ได้กำหนดไว้ในเอกสาร แต่จริง ๆ แล้วทำงานได้อย่างน่าเชื่อถือในฮาร์ดแวร์
เมื่อเล่น King of Fighters ตัวนับเวลาจะลดลงเหลือ 0 แล้วหมุนกลับไปที่ 99 ป้องกันไม่ให้รอบจบลงอย่างมีประสิทธิภาพ ในที่สุดฉันก็ติดตามมันไปจนถึงพฤติกรรมของ SBCD: ภายในชิปจริง ๆ แล้วอัปเดต overflow flag อย่างน่าเชื่อถือ (มันถูกทำเครื่องหมายว่าไม่ได้กำหนดไว้ในเอกสาร) SNK กำลังตรวจสอบ V flag และจบรอบเมื่อมันถูกตั้งค่า
วิธีการนี้ช่วยประหยัดเวลาการประมวลผลอย่างมาก เพราะการแยกตัวเลขจากค่า Binary Coded Decimal ต้องใช้เพียงการเลื่อนบิตอย่างง่าย (6 รอบ) เมื่อเทียบกับการดำเนินการหารที่มีราคาแพง (140 รอบ) สำหรับบริษัทที่ต่อสู้กับข้อจำกัดขนาด ROM เพื่อลดต้นทุน ทุกไบต์และทุกรอบจึงมีความสำคัญ
Binary Coded Decimal (BCD): การเข้ารหัสตัวเลขที่แต่ละหลักทศนิยมจะแสดงด้วย 4 บิต ทำให้แสดงตัวเลขได้ง่ายขึ้น แต่มีประสิทธิภาพน้อยกว่าสำหรับการคำนวณ
การเปรียบเทียบประสิทธิภาพคำสั่ง 68000:
- SBCD (Subtract Binary Coded Decimal): 6 รอบ
- การลบแบบไบนารี: 4 รอบ
- การดำเนินการหาร: 140 รอบ
- การเลื่อนบิตสำหรับการแยกหลัก BCD: 6 รอบ
ความแตกต่างของคอมไพเลอร์สมัยใหม่ในแพลตฟอร์มต่าง ๆ
การอภิปรายยังเน้นให้เห็นว่าเวอร์ชันคอมไพเลอร์ที่แตกต่างกันจัดการกับความท้าทายในการปรับปรุงเดียวกันอย่างไร ในขณะที่บทความต้นฉบับต้องการวิธีแก้ปัญหาที่ซับซ้อนเพื่อสร้างโค้ดลูปที่มีประสิทธิภาพ นักพัฒนาที่ทดสอบกับ toolchain 68000 อื่น ๆ พบผลลัพธ์ที่แตกต่างกัน GCC เวอร์ชันสมัยใหม่บางตัวที่มุ่งเป้าไปที่ Amiga สร้างคำสั่ง DBRA ที่ต้องการโดยอัตโนมัติโดยไม่ต้องใช้ function attribute พิเศษหรือการปรับโครงสร้างโค้ด
ความไม่สอดคล้องกันนี้บ่งบอกว่ากลยุทธ์การปรับปรุงคอมไพเลอร์ได้พัฒนาไปในทิศทางที่แตกต่างกันในสภาพแวดล้อมการพัฒนา 68000 ต่าง ๆ โดยบางตัวยังคงรักษาความตระหนักรู้ที่ดีกว่าเกี่ยวกับข้อได้เปรียบของชุดคำสั่งที่เป็นเอกลักษณ์ของโปรเซสเซอร์
Loop Unrolling: ระดับถัดไปของการปรับปรุง
สมาชิกชุมชนระบุโอกาสในการปรับปรุงเพิ่มเติมที่ไม่ได้ครอบคลุมในการสำรวจเดิมอย่างรวดเร็ว Loop unrolling กลายเป็นข้อเสนอแนะที่ได้รับความนิยม ซึ่งการดำเนินการหลายอย่างจะดำเนินการภายในการทำซ้ำลูปแต่ละครั้งเพื่อลด overhead ของคำสั่ง branching แทนที่จะเขียนค่าหนึ่งค่าต่อรอบลูป นักพัฒนาสามารถเขียนสี่หรือแปดค่า ปรับปรุงประสิทธิภาพอย่างมากสำหรับการดำเนินการจำนวนมากเช่นการล้างหน้าจอ
เทคนิคนี้มีประสิทธิภาพเป็นพิเศษในโปรเซสเซอร์ที่ไม่มีระบบแคชสมัยใหม่ ซึ่งความกังวลหลักคือการลด instruction overhead มากกว่าการจัดการรูปแบบการเข้าถึงหน่วยความจำ
ผลลัพธ์การปรับปรุงขนาดโค้ด:
- ลูปเริ่มต้นที่ยังไม่ได้ปรับปรุง: หลายคำสั่งพร้อมการใช้งาน stack
- ปรับปรุงด้วย -O2: 5 คำสั่งต่อการทำซ้ำ, 22 รอบนาฬิกา
- ปรับปรุงขั้นสุดท้ายด้วย DBRA: 1 คำสั่งต่อการทำซ้ำ, 16 รอบนาฬิกา
- การปรับปรุงการเข้าถึงหน่วยความจำ: จาก 6 ไบต์เป็น 2 ไบต์ต่อการทำซ้ำ
ผลกระทบที่กว้างขึ้นสำหรับการพัฒนาเรโทร
การค้นพบเหล่านี้เน้นให้เห็นการเปลี่ยนแปลงที่สำคัญในการพัฒนาคอมพิวเตอร์เรโทร นักพัฒนาสมัยใหม่ไม่จำเป็นต้องเขียนโปรเจ็กต์ทั้งหมดในภาษา assembly อีกต่อไป เนื่องจาก toolchain ร่วมสมัยสามารถสร้างโค้ดที่ปรับปรุงสูงเมื่อได้รับการแนะนำอย่างเหมาะสม อย่างไรก็ตาม การอภิปรายยังเน้นว่าการพึ่งพาการปรับปรุงคอมไพเลอร์อย่างสุ่มสี่สุ่มห้ามักนำไปสู่ความผิดหวัง
ข้อมูลเชิงลึกที่สำคัญคือการพัฒนาเรโทรที่มีประสิทธิภาพต้องการความเข้าใจทั้งฮาร์ดแวร์เป้าหมายและพฤติกรรมของคอมไพเลอร์ นักพัฒนาต้องรู้ว่าเมื่อไหร่ควรใช้ compiler hint เมื่อไหร่ควรปิดการปรับปรุงเฉพาะ และเมื่อไหร่ควรใช้ภาษา assembly สำหรับส่วนที่สำคัญ
ความสมดุลระหว่างประสิทธิภาพของภาษาระดับสูงและการปรับปรุงประสิทธิภาพระดับต่ำนี้แสดงถึงจุดที่เหมาะสมสำหรับการพัฒนาเรโทรสมัยใหม่ ช่วยให้นักพัฒนาสามารถรักษาความสามารถในการอ่านโค้ดในขณะที่บรรลุประสิทธิภาพที่จำเป็นสำหรับฮาร์ดแวร์เก่าที่มีทรัพยากรจำกัด