- ตัวจัดตาราง CPU ของเคอร์เนลมีโหมดการแย่งงานหลายแบบที่ออกแบบมาเพื่อสร้างจุดสมดุลระหว่างปริมาณงานโดยรวมของระบบกับเวลาในการตอบสนอง
- ในเดือนกันยายน 2023 ระหว่างการอภิปรายเรื่องการจัดตาราง ได้มีการเสนอแนวคิดที่เรียกว่า “การแย่งงานแบบขี้เกียจ (lazy preemption)” ซึ่งอาจให้ผลลัพธ์ที่ดีกว่าไปพร้อมกับทำให้การจัดตารางของเคอร์เนลง่ายขึ้น
- แนวคิดนี้เงียบหายไปพักหนึ่ง ก่อนจะกลับมาอีกครั้งผ่านชุดแพตช์ของ Peter Zijlstra
โหมดการแย่งงานของเคอร์เนลในปัจจุบัน
- PREEMPT_NONE: อนุญาตให้มีการแย่งงานได้ก็ต่อเมื่องานที่กำลังรันใช้ time slice หมดแล้วเท่านั้น
- PREEMPT_VOLUNTARY: เพิ่มจุดจำนวนมากภายในเคอร์เนลที่สามารถแย่งงานได้เมื่อจำเป็น
- PREEMPT_FULL: สามารถแย่งงานได้แทบทุกจุด ยกเว้นกรณีที่มีการถือ spinlock อยู่
- PREEMPT_RT: ให้ความสำคัญกับการแย่งงานมากกว่าสิ่งอื่นส่วนใหญ่ และทำให้โค้ด spinlock ส่วนใหญ่สามารถถูกแย่งงานได้ด้วย
การนำการแย่งงานแบบขี้เกียจมาใช้
- แพตช์การแย่งงานแบบขี้เกียจเพิ่มแฟล็กใหม่
TIF_NEED_RESCHED_LAZY เพื่อระบุว่าจำเป็นต้องจัดตารางใหม่ในบางจังหวะ แต่ไม่ใช่ในทันที
- ในโหมดการแย่งงานแบบขี้เกียจ (PREEMPT_LAZY) เหตุการณ์ส่วนใหญ่จะตั้งค่าแฟล็กใหม่นี้ และเมื่อเคอร์เนลกลับไปยัง user space หากมีการตั้งค่าแฟล็กใดแฟล็กหนึ่งจากสองแฟล็กนี้ไว้ ก็จะมีการเรียกใช้ตัวจัดตาราง
- ผลจากการเปลี่ยนแปลงนี้คือ ในโหมดการแย่งงานแบบขี้เกียจ เหตุการณ์ส่วนใหญ่ในเคอร์เนลจะไม่แย่งงานปัจจุบันทันที
การถอด cond_resched() ออก
- เป้าหมายสุดท้ายของงานนี้คือให้เหลือเพียงสองโหมดที่ไม่ใช่เรียลไทม์ คือ PREEMPT_LAZY และ PREEMPT_FULL
- โหมดแบบขี้เกียจจะอยู่กึ่งกลางระหว่าง PREEMPT_NONE และ PREEMPT_VOLUNTARY และจะเข้ามาแทนที่ทั้งสองโหมดนี้
- ปัจจุบันยังคงมีการเรียก
cond_resched() หลงเหลืออยู่ และยังจำเป็นตราบใดที่โหมด PREEMPT_NONE และ PREEMPT_VOLUNTARY ยังมีอยู่
สรุปโดย GN⁺
- การแย่งงานแบบขี้เกียจอาจช่วยทำให้การจัดตารางของเคอร์เนลง่ายขึ้น และช่วยให้ได้ค่า latency ที่คาดการณ์ได้
- งานนี้อาจช่วยลดขนาดของเคอร์เนลและทำให้โค้ดเรียบง่ายขึ้น
- การแย่งงานแบบขี้เกียจให้ throughput ใกล้เคียงกับ PREEMPT_VOLUNTARY แต่ยังต้องการการทดสอบและการปรับแต่งเพิ่มเติม
- โครงการอื่นที่มีฟังก์ชันคล้ายกันคือ ULE scheduler ของ FreeBSD
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ผลลัพธ์สุดท้ายของงานเรื่อง lazy preemption คือเคอร์เนลมีขนาดเล็กลงและเรียบง่ายขึ้น พร้อมทั้งให้ค่า latency ที่คาดการณ์ได้ ซึ่งดูเป็นทางออกที่ดีกว่าโดยไม่ต้องโปรยการเรียกที่เกี่ยวกับ scheduler ไว้ทั่วทั้งโค้ด อย่างไรก็ตาม กว่าจะไปถึงจุดนั้นคงต้องใช้เวลา
preemption ในระดับสูงช่วยให้ระบบตอบสนองต่อเหตุการณ์ได้เร็วขึ้น เหตุการณ์อย่างการขยับเมาส์หรือสัญญาณ "ใกล้หลอมละลาย" ของเครื่องปฏิกรณ์ หากตอบสนองได้เร็วก็ย่อมน่าพอใจกว่า อย่างไรก็ตาม preemption ในระดับสูงอาจกระทบต่อ throughput โดยรวมของระบบ เวิร์กโหลดที่มีงานแบบ CPU-intensive จำนวนมากจะได้ประโยชน์หากถูกรบกวนน้อยที่สุด การ preempt ที่ถี่ขึ้นอาจทำให้เกิด lock contention สูงขึ้นได้ จึงมีหลายโหมดอยู่ และโหมด preemption ที่เหมาะสมที่สุดก็น่าจะขึ้นอยู่กับเวิร์กโหลด
ปัจจุบันเคอร์เนลมีอยู่สี่โหมดสำหรับควบคุมว่าช่วงใดงานหนึ่งสามารถถูก preempt เพื่อให้อีกงานหนึ่งเข้ามาแทนได้
หาไม่เจอตัวเลขที่เกี่ยวข้องกับแพตช์ในเธรดที่ลิงก์ไว้ น่าจะมีการทำ benchmarking เบื้องต้นไว้บ้าง ซึ่งจะช่วยบอกศักยภาพที่แท้จริงของการเปลี่ยนแปลงนี้ได้
สงสัยว่า scheduler ผูกติดกับโค้ดส่วนอื่นของเคอร์เนลแน่นแค่ไหน
คงจะดีถ้า preemption ปรับตัวตามเหตุการณ์ได้ แต่การจัดการสิ่งนี้สำหรับทุกเหตุการณ์อาจกระทบต่อเสถียรภาพของระบบ เรื่องนี้คล้ายกับการใช้เครื่องมืออย่าง Tomba Finder เพื่อสร้างลีด