Thread-Per-Core vs. Work-Stealing: การอภิปรายความร้อนแรงเรื่องประสิทธิภาพของซีพียูยุคใหม่

ทีมชุมชน BigGo
Thread-Per-Core vs. Work-Stealing: การอภิปรายความร้อนแรงเรื่องประสิทธิภาพของซีพียูยุคใหม่

ในการไล่ล่าความเร็วอย่างไม่หยุดยั้ง สถาปนิกซอฟต์แวร์ต่างถกเถียงกันอย่างถึงพริกถึงขิงเกี่ยวกับวิธีใช้ประโยชน์จากโปรเซสเซอร์หลายคอร์ยุคใหม่ให้ดีที่สุด คำถามหลักคือควรเลือกรูปแบบ thread-per-core ที่ให้แต่ละคอร์จัดการงานของตัวเองแบบแยกส่วน หรือใช้แนวทาง work-stealing ที่คอร์ที่ว่างสามารถแย่งชิงงานจากคอร์ที่กำลัง繁忙อย่างไดนามิก การอภิปรายทางเทคนิคนี้ส่งผลกระทบอย่างลึกซึ้งต่อทุกอย่างตั้งแต่ประสิทธิภาพของฐานข้อมูลไปจนถึงปัญญาประดิษฐ์ โดยมีผู้สนับสนุนทั้งสองฝ่ายที่เต็มไปด้วยความปรารถนาดี

ความขัดแย้งหลัก: การแยกส่วน vs. ความยืดหยุ่น

สถาปัตยกรรม thread-per-core ทำงานบนหลักการง่ายๆ: มอบหมายงานให้คอร์เฉพาะและเก็บงานไว้ที่นั่น วิธีการนี้เพิ่มขีดสูงสุดของ data locality ทำให้มั่นใจได้ว่าข้อมูลที่เข้าถึงบ่อยๆ จะยังคงอยู่ในแคชท้องถิ่นของคอร์ ผลประโยชน์อาจจะน่าทึ่ง – นักพัฒนาคนหนึ่งที่ทำงานบนระบบจดจำใบหน้ารายงานว่าสามารถเปรียบเทียบใบหน้าได้หลายสิบล้านครั้งต่อวินาทีต่อคอร์โดยเก็บทุกอย่างไว้ใน L1 cache ประสิทธิภาพนี้มาจากการหลีกเลี่ยงการสื่อสารข้ามคอร์ที่มีค่าใช้จ่ายสูงซึ่งสามารถทำให้เกิดโทษความล่าช้าถึง 100 เท่าเมื่อเทียบกับการเข้าถึงแคชท้องถิ่น

อย่างไรก็ตาม ผู้สนับสนุน work-stealing โต้แย้งว่าการแยกตัวนี้มาพร้อมกับค่าใช้จ่าย เมื่อปริมาณงานกระจายไม่สม่ำเสมอ – ปัญหาทั่วไปที่รู้จักกันในชื่อ data skew – คอร์บางคอร์อาจจะว่างในขณะที่คอร์อื่นๆ ทำงานล้นมือ Work-stealing ปรับสมดุลโหลดเหล่านี้อย่างไดนามิก ทำให้มั่นใจในการใช้ประโยชน์จาก CPU โดยรวมที่ดีขึ้น การประนีประนอมคืองานต้องถูกออกแบบให้เคลื่อนย้ายระหว่างคอร์ได้ ซึ่งในภาษาเช่น Rust หมายความว่าพวกมันต้องเป็น Send – สามารถถ่ายโอนระหว่างเธรดได้อย่างปลอดภัย

ข้อแลกเปลี่ยนที่สำคัญของสถาปัตยกรรม

ด้าน Thread-Per-Core Work-Stealing
Data Locality ยอดเยี่ยม - ข้อมูลอยู่ใน cache ในเครื่อง แปรผัน - งานเคลื่อนย้ายระหว่างคอร์
Load Balancing ต้องแบ่งพาร์ติชันด้วยตนเอง สมดุลแบบไดนามิกอัตโนมัติ
Cross-Core Communication น้อยที่สุด จำเป็นสำหรับการขโมยงาน
Task Requirements สามารถใช้โครงสร้างข้อมูลที่ไม่ปลอดภัยต่อเธรด งานต้องปลอดภัยต่อเธรด (Send ใน Rust)
Ideal Workload คาดการณ์ได้ ผูกติดกับ memory-bandwidth แปรผัน งานที่หลากหลาย

การวิวัฒนาการของฮาร์ดแวร์ที่เปลี่ยนการคำนวณ

การอภิปรายไม่ได้เกิดขึ้นในสุญญากาศ – ความสามารถของฮาร์ดแวร์ได้พัฒนาอย่างมาก ตามที่ผู้แสดงความคิดเห็นคนหนึ่งระบุไว้ สิ่งต่างๆ เช่น ความเร็วดิสก์ได้พัฒนาอย่างมากในช่วง 10 ปีที่ผ่านมาในขณะที่ความเร็วของ CPU ไม่ได้พัฒนาขึ้น การเปลี่ยนแปลงนี้หมายความว่าแอปพลิเคชันจำนวนมากที่เคยถูกจำกัดโดย I/O ตอนนี้ถูกจำกัดโดย CPU ทำให้การใช้งานคอร์อย่างมีประสิทธิภาพสำคัญยิ่งกว่าที่เคย

แบนด์วิธหน่วยความจำได้ปรากฏเป็นคอขวดใหม่สำหรับปริมาณงานประสิทธิภาพสูงหลายอย่าง สถาปัตยกรรม thread-per-core เปล่งประกายเป็นพิเศษในสถานการณ์ที่ถูกจำกัดโดยแบนด์วิธหน่วยความจำ เนื่องจากพวกมันลดการแก่งแย่งแคชที่สามารถลดประสิทธิภาพลงได้ อย่างไรก็ตามเมื่อจำนวนคอร์ยังคงเพิ่มขึ้นอย่างต่อเนื่อง – ด้วยเซิร์ฟเวอร์ระดับไฮเอนด์ที่มีคอร์หลายสิบคอร์ในปัจจุบัน – ปัญหาของการไม่สมดุลของโหลดก็รุนแรงมากขึ้น คอร์เดียวที่โหลดเกินสามารถกลายเป็นคอขวดสำหรับระบบทั้งหมด

การประยุกต์ใช้ในโลกจริงและการเปลี่ยนแปลงทางวัฒนธรรม

การอภิปรายขยายเกินกว่าประสิทธิภาพทางทฤษฎีไปสู่ข้อกังวลในการปฏิบัติจริง นักพัฒนาที่มีประสบการณ์คนหนึ่งระบุว่าระบบ thread-per-core ได้พัฒนาอย่างมีนัยสำคัญ: สถาปัตยกรรมจากประมาณปี 2010 ค่อนข้างหยาบ ในขณะที่บทความนี้มีความถูกต้องสำหรับสถาปัตยกรรมจาก 10+ ปีที่แล้ว สถานะล่าสุดสำหรับ thread-per-core ในวันนี้ดูไม่เหมือนสถาปัตยกรรมเหล่านั้นเลย

ในระบบประมวลผลข้อมูล มีการยอมรับมากขึ้นว่า การแก้ปัญหา skew problem ในเลเยอร์ที่สูงขึ้น ไม่ได้เป็นไปได้เสมอไปในระดับขนาดใหญ่ ระบบเช่น DuckDB, KuzuDB และอื่นๆ ได้ยอมรับ morsel-driven parallelism ซึ่งแสดงถึงแนวทางแบบไฮบริดที่รักษา some locality ในขณะที่อนุญาตให้มีการกระจายซ้ำแบบไดนามิก สิ่งนี้สะท้อนให้เห็นถึงการเปลี่ยนแปลงทางวัฒนธรรมในการสร้างความยืดหยุ่นโดยตรงเข้าไปในระบบแทนที่จะพึ่งพาวิธีแก้ปัญหาภายนอก

ผมมองการสื่อสารข้ามคอร์เป็นโทษความล่าช้า 100 เท่า ทุกอย่างตามมาจากตรงนั้น

ข้อมูลเชิงลึกพื้นฐานนี้ขับเคลื่อนการสนับสนุน thread-per-core เป็นส่วนใหญ่ เมื่อการสื่อสารข้ามคอร์ทุกครั้งมีโทษที่หนักเช่นนั้น แรงจูงใจที่จะเก็บงานไว้ในท้องถิ่นก็กลายเป็นสิ่งที่น่าท่วมท้น อย่างไรก็ตาม ผู้สนับสนุน work-stealing โต้แย้งว่าการนำสมัยใหม่นั้นฉลาดเกี่ยวกับเมื่อใดที่ควรขโมยงาน – พวกมันไม่ได้กระจายงานใหม่อย่างสุ่มสี่สุ่มห้า แต่จะทำเฉพาะเมื่อประโยชน์มีค่ามากกว่าต้นทุนอย่างชัดเจน

มาตราวัดผลกระทบต่อประสิทธิภาพ

  • การเข้าถึง L1 Cache: ~1ns (จุดอ้างอิง)
  • การสื่อสารข้ามคอร์: ~100ns (ค่าปรับลดประสิทธิภาพ 100 เท่าของ L1)
  • Atomic Operations: 4-10ns (มีนัยสำคัญสำหรับเส้นทางที่มีการใช้งานสูง)
  • CAS Operations: ~20-50 ล้านการดำเนินการต่อวินาทีสูงสุด
  • Memory Bandwidth: คอขวดหลักสำหรับระบบประสิทธิภาพสูง (สูงสุดถึง 200 GB/s บนเซิร์ฟเวอร์สมัยใหม่)

อนาคตของการเขียนโปรแกรมพร้อมกัน

การแก้ไขการอภิปรายนี้อาจไม่ใช่ผลลัพธ์ที่ผู้ชนะได้ทั้งหมด ปริมาณงานที่แตกต่างกันมีลักษณะที่แตกต่างกัน และโซลูชันที่เหมาะสมที่สุดมักจะขึ้นอยู่กับกรณีการใช้งานเฉพาะ ปริมาณงานการคำนวณที่มีประโยชน์ของ locality ที่แข็งแกร่งชัดเจน favore แนวทาง thread-per-core ในขณะที่ปริมาณงานที่แปรผันและต่างกันมากกว่าอาจได้รับประโยชน์จากความยืดหยุ่นของ work-stealing

สิ่งที่ชัดเจนคือในขณะที่ฮาร์ดแวร์ยังคงพัฒนาต่อไป – ด้วยลำดับชั้นแคชที่ซับซ้อนมากขึ้น สถาปัตยกรรม NUMA และหน่วยประมวลผลพิเศษ – การสนทนาเกี่ยวกับกลยุทธ์การจัดตารางเวลาที่เหมาะสมที่สุดจะเติบโตขึ้นอย่างมีประสิทธิภาพมากขึ้น นักพัฒนาและสถาปนิกที่เข้าใจการประนีประนอมพื้นฐานเหล่านี้จะอยู่ในตำแหน่งที่ดีที่สุดในการควบคุมพลังเต็มที่ของโครงสร้างพื้นฐานการคำนวณสมัยใหม่

การอภิปรายระหว่าง thread-per-core กับ work-stealing แสดงถึงมากกว่าความพึงพอใจทางเทคนิค – มันเป็นคำถามพื้นฐานเกี่ยวกับวิธีที่เราควรจัดระเบียบการคำนวณในยุคแห่งความขนานที่อุดมสมบูรณ์ ในขณะที่ทั้งสองแนวทางยังคงพัฒนาต่อไป ระบบที่ประสบความสำเร็จมากที่สุดน่าจะรวมข้อมูลเชิงลึกจากทั้งสองปรัชญา ปรับให้เข้ากับความต้องการเฉพาะของแต่ละปริมาณงานในขณะที่เคารพความเป็นจริงของฮาร์ดแวร์พื้นฐาน

อ้างอิง: The Death of Thread Per Core