3 คะแนน โดย GN⁺ 2024-10-20 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ตัวจัดตาราง 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 ความคิดเห็น

 
GN⁺ 2024-10-20
ความคิดเห็นจาก Hacker News
  • ผลลัพธ์สุดท้ายของงานเรื่อง lazy preemption คือเคอร์เนลมีขนาดเล็กลงและเรียบง่ายขึ้น พร้อมทั้งให้ค่า latency ที่คาดการณ์ได้ ซึ่งดูเป็นทางออกที่ดีกว่าโดยไม่ต้องโปรยการเรียกที่เกี่ยวกับ scheduler ไว้ทั่วทั้งโค้ด อย่างไรก็ตาม กว่าจะไปถึงจุดนั้นคงต้องใช้เวลา

    • เช่นเดียวกับ EEVDF นี่คือการทำให้สภาพปัจจุบันเรียบง่ายขึ้นและดีขึ้น น่าจะไม่มีทางออกที่ดีกว่านี้
  • preemption ในระดับสูงช่วยให้ระบบตอบสนองต่อเหตุการณ์ได้เร็วขึ้น เหตุการณ์อย่างการขยับเมาส์หรือสัญญาณ "ใกล้หลอมละลาย" ของเครื่องปฏิกรณ์ หากตอบสนองได้เร็วก็ย่อมน่าพอใจกว่า อย่างไรก็ตาม preemption ในระดับสูงอาจกระทบต่อ throughput โดยรวมของระบบ เวิร์กโหลดที่มีงานแบบ CPU-intensive จำนวนมากจะได้ประโยชน์หากถูกรบกวนน้อยที่สุด การ preempt ที่ถี่ขึ้นอาจทำให้เกิด lock contention สูงขึ้นได้ จึงมีหลายโหมดอยู่ และโหมด preemption ที่เหมาะสมที่สุดก็น่าจะขึ้นอยู่กับเวิร์กโหลด

    • สงสัยว่าทำไมระดับของ preemption จึงเป็นคุณสมบัติของโหมดแบบ global ไม่ใช่คุณสมบัติของเหตุการณ์เฉพาะ บางเหตุการณ์ควรถูกจัดการด้วย latency ที่ต่ำกว่าเหตุการณ์อื่น
  • ปัจจุบันเคอร์เนลมีอยู่สี่โหมดสำหรับควบคุมว่าช่วงใดงานหนึ่งสามารถถูก preempt เพื่อให้อีกงานหนึ่งเข้ามาแทนได้

    • สงสัยว่านี่หมายถึงงานในเคอร์เนล งานของผู้ใช้ หรือทั้งสองอย่าง
  • หาไม่เจอตัวเลขที่เกี่ยวข้องกับแพตช์ในเธรดที่ลิงก์ไว้ น่าจะมีการทำ benchmarking เบื้องต้นไว้บ้าง ซึ่งจะช่วยบอกศักยภาพที่แท้จริงของการเปลี่ยนแปลงนี้ได้

  • สงสัยว่า scheduler ผูกติดกับโค้ดส่วนอื่นของเคอร์เนลแน่นแค่ไหน

    • ตัวอย่างเช่น ถ้าต้องการทำให้ scheduler เรียบง่ายลงอย่างมากสำหรับแอปพลิเคชันทางวิทยาศาสตร์ที่ไม่สนใจเรื่อง preemption เลย จะสามารถทำแบบนั้นได้อย่างสะอาดและเป็นโมดูลาร์หรือไม่ และจะได้ประโยชน์อะไรบ้าง
  • คงจะดีถ้า preemption ปรับตัวตามเหตุการณ์ได้ แต่การจัดการสิ่งนี้สำหรับทุกเหตุการณ์อาจกระทบต่อเสถียรภาพของระบบ เรื่องนี้คล้ายกับการใช้เครื่องมืออย่าง Tomba Finder เพื่อสร้างลีด

    • ต้องรักษาสมดุลระหว่างความแม่นยำ (ลีดเป้าหมาย) กับประสิทธิภาพ เพื่อให้ภาพรวมทำงานได้อย่างราบรื่น