180 likes | 276 Views
Register Spilling. การ ล้นจาก รีจิสเตอร์. Spilling (การล้นจากรีจิสเตอร์). เกิดอะไรขึ้นถ้าทุกๆ node มี neighbor มากกว่า k ตัวอย่างเช่นถ้าเราต้องการทำ 3-coloring ของ RIG ด้านล่างนี้. Spilling (การล้นจากรีจิสเตอร์).
E N D
Register Spilling การล้นจากรีจิสเตอร์
Spilling (การล้นจากรีจิสเตอร์) • เกิดอะไรขึ้นถ้าทุกๆ node มี neighbor มากกว่า k • ตัวอย่างเช่นถ้าเราต้องการทำ 3-coloring ของ RIG ด้านล่างนี้
Spilling (การล้นจากรีจิสเตอร์) • • ถ้าเราลบ node หนึ่งออกไปตามขั้นตอน heuristics เราจะไปต่อไปได้ดังแสดงด้วยรูป RIG ใหม่ต้านล่าง • • ในกรณีนี้เราจะทำการเลือก node ที่จะ spill • – ตัวแปรที่ถูก spill ไปจะไป “lives” ในหน่วยความจำหลักแทน • – ให้ว่าเราเลือก f ที่จะต้องถูก spill ไปที่หน่วยความจำหลัก
Spilling (การล้นจากรีจิสเตอร์) • หลังจากที่เรา spill f แล้ว เราสามารถกลับไปใช้ heuristic ได้เหมือนเดิม (ดูจากภาพ RIG ด้านล่าง)
Spilling (การล้นจากรีจิสเตอร์) • ในท้ายที่สุดเราก็จะต้องให้ค่าสีกับ f โดยความคาดหวังของเราก็คือจาก neighbor ทั้ง 4 ของ f นั้น บางทีอาจจะมีสีที่ใช้แตกต่างกัน น้อยกว่า 3 สีก็เป็นได้
Spilling • ในกรณีนี้ไม่เป็นเช่นที่เราคาดหวัง • เราจะต้องทำการจองตำแหน่งในหน่วยความจำหลักให้กับ f • ส่วนใหญ่ใช้พื้นที่บน stack และจัดให้อยู่ใน AR • เรียนตำแหน่งนี้ว่า fa • ในกรณีที่มีการใช้ f ให้เพิ่มคำสั่ง load เพื่อนำข้อมูลเข้าเข้ามาที่ f f := load fa • ในกรณีที่มีการ assign ค่าให้ f (การ write ไปที่ f) ให้ใส่คำสั่ง store ต่อไปนี้ store f, fa
ตัวอย่าง Spilling • โค๊ดใหม่หลังจากการ spill ตัวแปร f
คำนวณ Livenessใหม่ • ข้อมูล livenessที่คำนวณได้ใหม่มีความใกล้เคียงกับ livenessเดิมมาก (สำหรับ CFG ที่ไม่มีการ spill) • f จะ live ในช่วงต่อไปนี้ • ระหว่าง f := load faและคำสั่งที่อยู่ถัดไป • ระหว่าง f := load faและคำสั่งที่อยู่ก่อนหน้านี้ • การทำ spilling ลดช่วงชีวิต (live range) ของ f • เพราะฉะนั้นทำให้ interference (การทับหรือรบกวนกันของตัวแปร) ลดลง • ซึ่งทำให้ node ของ f ใน RIG มี neighbor ที่น้อยลง
คำนวณ RIG ใหม่หลังจาก Spilling • Edge หลังจากที่ node ที่ทำการ spill จะถูกกำจัดออกจาก RIG เดิม • ตัวแปร f live range ยังทับซ้อนกับ live range ของ c และ d • ได้ RIG ใหม่ที่สามารถใช้เพียง 3 สีในการให้สี RIG นี้ได้ (3-colorable)
การตัดสินใจเกี่ยวกับ Spilling • ในบางครั้งเราอาจจะต้อง spill มากกว่าหนึ่งครั้งก่อนที่จะทำ coloring จากจำนวนสีที่กำหนดให้อย่างจำกัดได้ • เราสามารถเลือกที่จะ spill ตัวแปรตัวใดก็ได้ อัลกอริทึมที่กล่าวมายังคงทำงานได้ถูกต้อง • แต่การเลือกที่ดีจะช่วยเพิ่มประสิทธิภาพของโค๊ด assembly ที่ผลิตได้ • มี heuristics ที่ใช้ในการเลือกต่อไปนี้: • Spill ตัวแปรที่ทำให้เกิด interference มากที่สุด (นั่นคือมี neighbor มากที่สุด) • Spill ตัวแปรที่ใช้งานน้อย • หลีกเลี่ยงการ spill ใน inner loop
ประเด็นหลักเกี่ยวกับ Register Allocation • คอมไพเลอร์ในปัจจุบันทำ optimization อันนี้ทั้งนั้น • เพราะสมรรถนะที่แตกต่างกันมากระหว่าง CPU กับ memory การคำนวณที่เกี่ยวข้องกับตัวแปรที่อยู่ในรีจิสเตอร์จะทำได้เร็วกว่าตัวแปรที่อยู่ใน memory มาก • การผลิตโค๊ดในสไตล์ stack machine จะมีสมรรถนะต่ำมากเพราะมีการโต้ตอบกับ memory มาก • การทำ register allocation สำหรับ CISC จะยุ่งยากมากกว่า RISC • รีจิสเตอร์แต่ละตัวมีสิ่งที่พิเศษเฉพาะตัว อาจจะไม่สามารถนำมาใส่ตัวแปรบางชนิดในทุกๆกรณีได้
ลำดับชั้นของระบบหน่วยความจำ(Memory Hierarchy)
การจัดการหน่วยความจำลำดับชั้นการจัดการหน่วยความจำลำดับชั้น • ภาพของหน่วยความจำที่โปรแกรมเมอร์เห็นคือที่เก็บข้อมูลที่มีตำแหน่งติดกันและมีความจุเป็นอนันต์และมีประสิทธิภาพสูง (Virtual Memory) • CPU OS และคอมไพเลอร์จะทำหน้าที่จัดการลำดับชั้นของหน่วยความจำเพื่อให้ภาพในอุดมคตินั้นกับโปรแกรมเมอร์ • ในโลกความเป็นจริงลำดับชั้นของหน่วยความจำมีความจำกัดทางด้านความจุและสมรรถนะ • CPU OS และ คอมไพเลอร์จะต้อง “ซ่อน” ข้อจำกัดนี้และ “หลอก” โปรแกรมเมอร์ให้มองเห็นแต่ในโลกเสมือน • CPU จัดการในส่วนที่เกี่ยวข้องกับ main memory และ cache • คอมไพเลอร์จัดการในส่วนการบริหารรีจิสเตอร์และการติดต่อกับ main memory • OS จัดการในส่วนที่เกี่ยวข้องกับ main memory และ disc
Cache • คอมไพเลอร์เก่งเรื่องการจัดการรีจิสเตอร์ • ถ้าเราต้องการให้คอมไพเลอร์ช่วย CPU ในการจัดการ cache • ทำได้ระดับหนึ่งแต่คอมไพเลอร์ไม่เก่งมาก • หลายๆอย่างโปรแกรมเมอร์ต้องออกมาจากโลกเสมือนเพื่อช่วยคอมไพเลอร์ • เป็นหนึ่งในแนวทางการวิจัยทางด้านคอมไพเลอร์ในปัจจุบัน
การ Optimize การใช้ Cache • พิจารณา loop ด้านล่างนี้ for(j := 1; j < 10; j++) for(i=1; i<10000000; i++) a[i] *= b[i] • Loop นี้มีประสิทธิภาพต่ำเพราะมีการใช้งาน cache อย่างไม่มีประสิทธิภาพ • ทุกๆการอ้างถึงค่าใน a[i] หรือ b[i] เป็นการ miss ทั้งหมด (ให้ว่า cache block มีขนาดเท่ากับหนึ่ง element ของ a และ b)
การ Optimize การใช้ Cache • พิจารณาการเขียนโปรแกรมใหม่จากสไลด์ที่แล้วดังนี้: for(i=1; i<10000000; i++) for(j := 1; j < 10; j++) a[i] *= b[i] – ให้ผลลัพธ์เหมือนกันแต่สมรรถนะของโปรแกรมนี้สูงขึ้นมาก – การใช้ประโยชน์จาก cache ทำได้สูงสุด – สมรรถนะที่เพิ่มขึ้นอาจจะมากกว่า 10 เท่า • Optimization ในลักษณะนี้ (เรียกว่า loop interchange) คอมไพเลอร์สามารถทำได้ • อาจจะต้องทำในระดับ high-level IR