1 คะแนน โดย GN⁺ 2024-07-28 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • AMD Zen 5 นำ 2-Ahead Branch Predictor มาใช้ท่ามกลางการ ออกแบบใหม่ทั้งส่วนหน้า ของสถาปัตยกรรม Zen และดึงงานวิจัยการทำนายสาขาแบบนำหน้าหลายบล็อกจากทศวรรษ 1990 กลับมาสู่การขยายส่วนหน้า x86 สมัยใหม่
  • การทำนายสาขาเป็นกลไกที่ทำให้ CPU frontend ดึงคำสั่งต่อไปได้แม้ผลของ conditional branch จะยังไม่ออกมา ส่วนการทำนายผิดจะนำไปสู่ pipeline flush และต้นทุนในการเริ่มใหม่
  • Zen 5 มี fetch pipe 2 ชุดที่ 32 ไบต์/รอบ จาก L1 instruction cache ขนาด 32KB โดยแต่ละชุดเชื่อมกับ 4-wide decode cluster และ Op Cache ก็เป็นโครงสร้าง dual-ported 6-wide ที่จ่าย operand ได้สูงสุด 12 รายการให้ Op Queue
  • โครงสร้างใหม่นี้ประมวลผล taken branch 2 รายการ ในหนึ่งรอบข้ามบล็อกคำสั่งที่ไม่ต่อเนื่องกัน และใช้ฟิลด์ความยาว 5 บิตเพื่อหาจุดเริ่มต้นของ prediction window ที่สาม ลดการใช้ทรัพยากร decode และ Op Cache เกินจำเป็น
  • เนื่องจาก x86 มีคำสั่งความยาวแปรผัน จึงทำให้การตัดสินขอบเขตคำสั่งแบบขนานทำได้ยาก ดังนั้น 2-Ahead Branch Predictor และโครงสร้าง fetch·decode แบบคู่จึงกลายเป็นการเปลี่ยนแปลงหลักในการขยาย frontend ของตระกูล Zen

จุดที่ frontend ของ Zen 5 เปลี่ยนไป

  • AMD แนะนำ Zen 5 ว่าเป็นการ ออกแบบใหม่ทั้งส่วนหน้า ของสถาปัตยกรรม Zen และ 2-Ahead Branch Predictor ก็เป็นส่วนหนึ่งที่โดดเด่นในความเปลี่ยนแปลงนั้น
  • โครงสร้างนี้อิงจากแนวคิดที่เคยกล่าวถึงในเปเปอร์เมื่อ 30 ปีก่อน และกลับมาปรากฏอีกครั้งท่ามกลางแนวโน้มที่ต้องการยกระดับประสิทธิภาพ single core ให้สูงขึ้น
  • หากทำนาย taken branch หลายรายการได้ในครั้งเดียว frontend จะสามารถมองล่วงหน้าไปถึง instruction stream ที่อยู่ไกลกว่าเดิมได้

เหตุผลที่ CPU pipeline ต้องมีการทำนายสาขา

  • CPU สมัยใหม่ใช้โครงสร้าง pipeline ที่แบ่งการดึงคำสั่ง (fetch), decode และ execution ออกเป็นหลายขั้น
  • เมื่อมี conditional branch frontend ต้องตัดสินตำแหน่งคำสั่งถัดไปที่จะดึงมาก่อนที่การประเมินเงื่อนไขจะเสร็จ
  • ตัวเลือกหลักมีสองแบบ
    • หยุดรอจนกว่าผลของ branch จะออกมา
    • ทำนายเส้นทางถัดไป และถ้าผิดก็ทิ้งงานที่ทำผิดไป แล้วเริ่มใหม่จากจุดที่แน่นอน
  • หากทำนายผิด จะต้องทำ flush เพื่อนำงานที่พึ่งพาการคาดเดานั้นออกจาก pipeline
  • ต้นทุนของการหยุดที่เงื่อนไข branch แปรผันตามจำนวนขั้นของ pipeline ระหว่าง instruction fetch กับการประเมิน branch condition ที่เสร็จสิ้น
  • สุดท้าย CPU จึงต้องทำนายลำดับการไหลของคำสั่งในโปรแกรมให้แม่นยำที่สุดเท่าที่เป็นไปได้

บริบทที่ทำให้แนวคิด 2-Ahead เกิดขึ้น

  • วิธีทำนายแบบง่ายเคยมีกลยุทธ์ที่มอง backward jump สั้น ๆ ว่าเป็น taken เสมอ ซึ่งในอดีตให้ประโยชน์มากกว่าวิธีที่ดึงที่อยู่ถัดไปเสมอ
  • หากเก็บสถานะเล็ก ๆ เช่นประวัติ branch ล่าสุดหรือบันทึก address ก็จะได้ผลลัพธ์ที่ดีกว่าในโปรแกรมจริง
  • แม้ใช้โครงสร้างขนาดเพียงหลักสิบ KB ก็สามารถทำอัตราทำนาย branch ได้ใน ช่วงปลาย 90%
  • 2-Ahead Branch Predictor เป็นข้อเสนอที่มีการถกเถียงมาตั้งแต่ต้นทศวรรษ 1990 และในตอนนั้นก็มีการพูดถึงปัญหาการขยายความกว้างของสถาปัตยกรรมเป็น 8-wide หรือมากกว่าควบคู่กัน
  • ต่อมาเมื่อ CPU เชิงพาณิชย์ย้ายไปสู่ multicore พื้นที่ของแต่ละคอร์จึงสำคัญขึ้น และแวดวงวิชาการก็หันไปมุ่งปรับปรุง TAGE predictor ที่มีประสิทธิภาพต่อพื้นที่มากกว่า
  • เมื่อการย่อกระบวนการผลิตทำให้ใส่ทรานซิสเตอร์ได้มากขึ้นในพื้นที่ใกล้เคียงเดิม ความสนใจต่อประสิทธิภาพ single core ก็เพิ่มขึ้นอีกครั้ง ควบคู่กับกระแสการขยายไปถึง CPU แบบ out-of-order หลายร้อยตัว

เหตุผลที่ frontend ของ x86 ยุ่งยากกว่า

  • ISA ที่ใช้คำสั่งความยาวคงที่อย่าง 64-bit Arm สามารถ decode ส่วนใดก็ได้ของ instruction cache line แบบขนานได้ง่าย
    • แค่แบ่งข้อมูลอินพุตตามขอบ byte ของคำสั่งที่รับประกันไว้ แล้วทำซ้ำ decoder logic
  • x86 มีความยาวคำสั่งแบบแปรผัน จึงต้อง parse instruction byte แบบเชิงเส้นเพื่อรู้ขอบเขตถัดไปของแต่ละคำสั่ง
  • สามารถทำขนานได้บางส่วนด้วย pipelining เช่น partial decode ของ prefix ก่อน แต่ต้นทุนบน x86 ไม่ได้ต่ำ
  • นี่เป็นเบื้องหลังที่ทำให้ 4-wide decode พบได้ทั่วไปในคอร์ x86 ที่เน้นประสิทธิภาพมาอย่างยาวนาน
  • การออกแบบอย่าง 6-wide decode ของ Intel Golden Cove เป็นไปได้เชิงพาณิชย์เพราะความหนาแน่นของลอจิกในกระบวนการผลิตสมัยใหม่เพิ่มขึ้น แต่ต้นทุนด้านพื้นที่และพลังงานของ monolithic parallel x86 decoding จะเพิ่มแบบ super-linear ตามการเพิ่ม width
  • โค้ดจำนวนเต็มของแอปพลิเคชันทั่วไปมี branch density ประมาณ branch 1 รายการต่อคำสั่ง 5~6 คำสั่ง ทำให้แรงจูงใจในการขยายความกว้างของ decoder ไปมากกว่านั้นลดลง
  • frontend ของ x86 ต้องการวิธีข้ามขั้นตอนการตัดสินขอบเขตคำสั่งที่ทำขนานได้ยาก และไปถึงขอบเขตคำสั่งถัดไปได้อย่างปลอดภัย

การใช้งาน fetch·decode แบบคู่ใน Zen 5

  • “Multiple-block ahead branch predictors” โดย Seznec et al. เป็นเปเปอร์หลักที่อธิบายเหตุผลและวิธีใช้งาน 2-Ahead Branch Predictor
  • แค่มี predictor ที่จัดการ taken branch หลายรายการยังไม่พอ และ Seznec et al. เห็นว่าหากต้องการใช้ประโยชน์โดยไม่ทำให้ความต้องการด้านพื้นที่พุ่งสูง จำเป็นต้องมี instruction fetch dual-porting
  • Zen 5 เปลี่ยน instruction fetch และ Op Cache เป็นโครงสร้าง dual-port
    • ใช้ fetch pipe 2 ชุดที่ 32 ไบต์/รอบ จาก L1 instruction cache ขนาด 32KB
    • fetch pipe แต่ละชุดเชื่อมต่อกับ 4-wide decode cluster ของตัวเอง
    • Op Cache เป็นดีไซน์ dual-ported 6-wide และจ่าย operand ได้สูงสุด 12 รายการ ให้ Op Queue
  • Branch Target Buffer ก็รวมอยู่ในส่วนที่ทำ dual-porting ด้วย
    • ขนาดใหญ่ของ L1 BTB ใน Zen 5 ที่เข้าถึงได้ 16K entries อาจอธิบายความเป็นไปได้ของ L1 BTB แบบ dual-ported
    • L2 BTB มี 8K entries ซึ่งเล็กกว่า L1 BTB
    • AMD ใช้ L2 BTB คล้ายกับ victim cache โดย entry ที่ถูก evict จาก L1 BTB จะย้ายไปยัง L2 BTB

วิธีประมวลผล taken branch 2 รายการในหนึ่งรอบ

  • Zen 5 สามารถประมวลผล taken branch 2 รายการ ในหนึ่งรอบข้ามบล็อกคำสั่งที่ไม่ต่อเนื่องกันได้
  • การเปลี่ยนแปลงนี้ลดการสูญเสีย fetch bandwidth เมื่อพบ taken branch และทำให้ทำนายข้าม taken branch สองรายการไปได้
  • สามารถมอง instruction stream หลัง taken branch ที่สองได้ไกลขึ้น จึงมี prediction window ได้สามชุด
  • prediction window ทั้งสามสามารถใช้ในการผลิตคำสั่งสำหรับ decode ได้ทั้งหมด
  • prediction window ที่สองมี length field 5 บิต แนบอยู่
    • ป้องกันสถานการณ์ที่ทรัพยากร decode หรือ Op Cache ถูกจองมากเกินไป
    • มีขนาดเล็กกว่า pointer แต่ให้จุดเริ่มต้นของ prediction window ที่สามได้
    • เมื่อ prediction window ที่สามข้าม cache line boundary ก็ไม่จำเป็นต้องเก็บสถานะเพิ่มเติมใน prediction lookup index สำหรับรอบถัดไป
    • หาก prediction window ที่สามอยู่ใน cache line เดียวกับ prediction window แรกหรือที่สอง partial third window นั้นจะไม่ได้ผลเท่ากับ prediction window ที่สามแบบเต็ม

ข้อจำกัดที่ยังเหลือใน SMT

  • เมื่อเปิดใช้งานสองเธรดใน Zen 5 fetch pipe ที่ผูกกับ decode cluster จะถูก แบ่ง partition แบบคงที่
  • ในสถานะนี้ หากจะทำงานเหมือน dual fetch core จำเป็นต้อง fetch จากทั้ง L1 instruction cache และ Op Cache
  • เหตุผลที่ AMD ทำ Op Cache เป็น dual-port อาจเพื่อรักษา dual fetch pipeline ให้ทำงานได้ดีขึ้น

เปเปอร์ที่เกี่ยวข้องซึ่งถูกกล่าวถึงร่วมกัน

1 ความคิดเห็น

 
GN⁺ 2024-07-28
ความคิดเห็นใน Hacker News
  • มีบทความที่อธิบายการทำนายการแตกแขนงได้ดีตั้งแต่การอิมพลีเมนต์ช่วงแรก ๆ คือ https://danluu.com/branch-prediction/

  • อยากรู้ว่าประสิทธิภาพของ SMT จะออกมาเป็นอย่างไร และคาดว่าแนวทางครั้งนี้จะให้ผลดีพร้อมถูกขัดเกลาเพิ่มในรุ่นถัดไป
    Zen5c ไปได้ถึง 192 คอร์ หรือ 384 vCPU และใน Zen 6c ปีหน้าน่าจะไปถึง 256 คอร์ได้ ถ้าเป็นเซิร์ฟเวอร์ 1U แบบดูอัลซ็อกเก็ตก็อาจกลายเป็น 512 คอร์, 1024 vCPU ได้
    ปัญหาการสเกลเว็บแอปที่เคยเจอในปี 2014 ตอนนี้ถ้าระบายความร้อนได้ก็น่าจะยัดลงในเซิร์ฟเวอร์เครื่องเดียวได้แล้ว แค่คิด 1 RPS ต่อ vCPU ก็ได้ 1000 RPS แบบยังไม่รวม cache hit และแม้แต่หน้าแรกของ HN เองก็คงไม่ได้โดน 1000 pageview ต่อวินาทีจากเซิร์ฟเวอร์

    • การเสิร์ฟเว็บเพจเป็นงานต้นทุนต่ำ จึงมีโอกาสสูงว่าจะชนข้อจำกัดด้าน network I/O ก่อนที่คอร์จะตัน
      แต่อยากรู้ว่าประสิทธิภาพฝั่ง HPC จะเป็นอย่างไร เรื่องการระบายความร้อนอาจไม่ใช่ปัญหาใหญ่ในตัวมันเอง แต่พอถึงจุดหนึ่งก็อาจกลายเป็นปัญหาแบบเปียกที่ต้องใช้ direct liquid cooling (DLC)
    • เช่นเดียวกับที่คอมพิวเตอร์ก้าวเข้าสู่ยุคกิโลไบต์ในทศวรรษ 1940 ตอนนี้เรากำลังก้าวเข้าสู่ยุคกิโลคอร์
      ถ้านับแร็กเซิร์ฟเวอร์แบบหนาแน่นที่มี GPU ติดอยู่เป็นเครื่องเดียว เราก็มาถึงระดับหลายร้อยกิโลคอร์แล้ว
      เคยเถียงกับคนที่ไม่เข้าใจแนวคิดว่าบริการขนาด Wikipedia สามารถรันบนเซิร์ฟเวอร์เครื่องเดียวได้ ซึ่งก็ค่อนข้างสนุก เพราะจริง ๆ มันเป็นเรื่องง่ายมาพักใหญ่แล้ว เพียงแต่ไม่ทำกันเพราะเหตุผลเชิงปฏิบัติอย่างความพร้อมใช้งานหรือความคุ้มค่าด้านต้นทุน
    • อาจจะตรงกันข้ามก็ได้ ยิ่งใช้ pipeline ของ CPU ได้ดีขึ้นเท่าไร ก็ยิ่งเหลือช่องให้สอดเธรดที่สองน้อยลง ทำให้ข้อดีของ SMT อาจลดลง
    • SMT ยังต้องการการตรวจสอบด้วยเบนช์มาร์กอีกมาก
      ตามสัญชาตญาณ ถ้ามีงานเพิ่มขึ้นที่แก้ปัญหาเดิมด้วยความเร็วครึ่งหนึ่ง ก็น่าจะมีต้นทุนด้านการใช้หน่วยความจำอยู่บ้าง เลยสงสัยว่ามีแอปที่พอเปิด SMT แล้วใช้หน่วยความจำเพิ่ม แต่ไม่ได้ความเร็วเพิ่มเลยอยู่บ่อยไหม
      จากเบนช์มาร์กที่เผยแพร่ออกมาหลายตัว ดูเหมือนว่าแอปส่วนใหญ่ไม่ได้ประโยชน์ด้านความเร็วรันอย่างชัดเจนนัก
    • ปัญหาการสเกลเว็บแอปมักเกิดแถว ๆ latency ของฐานข้อมูล
  • เป็นเรื่องน่าสนใจเสมอเมื่อเห็นงานวิจัยอายุหลายสิบปีซึ่งตอนออกมาแทบไม่มีใครสนใจ แต่พอฮาร์ดแวร์แรงพอแล้วกลับกลายเป็นเทคโนโลยีล้ำหน้าขึ้นมาทันที
    ตัวอย่างเช่น Z-buffer ที่ใช้ในวิดีโอเกม 3D ตอนปรากฏในเปเปอร์ครั้งแรกมันแทบถูกพูดถึงเป็นเรื่องประกอบ เพราะไม่ใช่ว่าหัวข้อไม่ดี แต่เพราะต้องใช้หน่วยความจำมากเกินไป
    หลายสิบปีต่อมา เมกะไบต์มีราคาถูกลงมากพอสมควร และท้ายที่สุด real-time 3D renderer ทุกตัวก็ใช้มัน

    • อีกตัวอย่างคือ low-density parity-check code Robert Gallager ค้นพบมันในปี 1962 แต่ตอนนั้นไม่สมจริงในเชิงการคำนวณ จึงถูกทิ้งและถูกลืมไปหลายสิบปี
      ดูเหมือนว่าจะมีช่องว่างในวรรณกรรมอยู่ราว 38 ปี จนกระทั่ง David MacKay ค้นพบมันใหม่
      การใช้งานกระแสหลักครั้งแรกคือในปี 2003 และตอนนี้มันถูกใช้ใน WiFi, Ethernet และ 5G
      [1] https://en.wikipedia.org/wiki/Low-density_parity-check_code
      [2] https://scholar.google.com/scholar?q=%22low+density+parity+c...
    • บางครั้งก็อดคิดไม่ได้ว่าสำหรับวิศวกรแล้วอาจมีเส้นทางอาชีพเชิงวิชาการแบบนี้ซ่อนอยู่
      ถ้าไปห้องสมุดแล้วอ่านงานที่นักวิจัยวิทยาการคอมพิวเตอร์เคยตีพิมพ์เป็นเปเปอร์กระดาษ อาจมีไอเดียที่ตอนนั้นยังทำไม่ได้ แต่ตอนนี้นำมาทำจริงได้แล้วก็ได้
    • มากกว่าจะบอกว่าฮาร์ดแวร์แรงขึ้นจนทำได้ ผมมองว่ามันใกล้กับการที่ดีไซน์แบบนั้นทำได้มาตั้งแต่หลายสิบปีก่อนแล้ว แต่เพิ่งจะน่าสนใจภายใต้ชุด trade-off แบบปัจจุบัน
      ตลอด 20 ปีที่ผ่านมา ประสิทธิภาพ single-core ถูกกดไว้เพราะให้ความสำคัญกับการสเกลแนวนอนหรือเพิ่มจำนวนคอร์ก่อน ทำให้ความซับซ้อนและพื้นที่ไดของแต่ละคอร์กลายเป็นประเด็น ถ้าแนวโน้มนี้ไม่เกิดขึ้นและผู้ออกแบบ CPU ยังมุ่งที่ประสิทธิภาพ single-core เป็นหลัก เราอาจได้เห็นการอิมพลีเมนต์แบบนี้เร็วกว่านี้มาก
      Z-buffer เป็นแนวคิดที่เรียบง่าย เลยอาจดูเหมือนประเด็นรองในเปเปอร์ ตัวอย่างที่ดีกว่าอาจเป็น ray tracing ซึ่งแม้ไม่มีพื้นฐานกราฟิก 3D ก็ยังเข้าใจแนวคิดได้ค่อนข้างชัด แต่จนไม่นานมานี้มันยังไม่สมจริงในแง่ประสิทธิภาพสำหรับการเรนเดอร์แบบเรียลไทม์
      สิ่งที่น่าสนใจก็คือ เราหาแนวทางที่ง่ายกว่าเพื่อประมาณการเรนเดอร์ที่เหมือนจริงไม่ได้ จนสุดท้ายต้องย้อนกลับไปใช้วิธีแก้เก่า ๆ ที่ค่อนข้างตรงไปตรงมาและมีต้นทุนสูง
    • อีกตัวอย่างคือ borrow checker ของ Rust ซึ่งมีรากมาจากเปเปอร์เรื่องระบบชนิดแบบ substructural เมื่อหลายสิบปีก่อน
      นักวิชาการจำนวนมากเคยมองว่าระบบชนิดแบบ substructural ตายไปแล้วในทางปฏิบัติ เพราะแพ้ให้กับ garbage collection แต่ Rust ชุบชีวิตมันขึ้นมาอีกครั้งด้วยการผสานเข้ากับไอเดียใหม่ ๆ ของ C++ ในยุคนั้น
    • Z-buffer ไม่ได้ต้องการแค่หน่วยความจำเพิ่มขึ้นเท่ากับ frame buffer หนึ่งชุดเท่านั้น แต่ยังต้องการแบนด์วิดท์การอ่าน/เขียนต่อพิกเซลอีกมากด้วย
      ความต้องการแบนด์วิดท์หน่วยความจำที่เพิ่มขึ้นนี้เองที่ทำให้การอิมพลีเมนต์อย่างถูกต้องทำได้ยากและมีราคาแพง ระบบระดับสูงใช้ช่อง RAM เฉพาะทาง แต่ในฮาร์ดแวร์ราคาถูกกว่ากลับไปแย่งแบนด์วิดท์จากอินเทอร์เฟซหน่วยความจำแบบใช้ร่วมกันอย่างมาก
      ตัวอย่างเช่น เกม N64 บางเกมปิด Z-buffer และไปปรับแต่งการวาดฉากหลัง/ฉากหน้าในซอฟต์แวร์แทน เพื่อหลีกเลี่ยงต้นทุนของการอ่านและอัปเดตข้อมูลความลึก
  • ตัวทำนายเชิงคาดเดา เคยเป็นเป้าหมายของการโจมตีหลายแบบเพื่อดึงข้อมูลลับออกมา
    ถ้า ISA ทั่วไปจำนวนมากมีช่องโหว่ ก็สงสัยว่ามีมาตรการอะไรเพื่อลดผลกระทบจากการโจมตีแบบนี้หรือไม่

    • ช่องโหว่ไม่ได้อยู่ที่การทำนายสาขา แต่คือ การประมวลผลแบบคาดเดา ตัวทำนายสาขาเป็นเพียงเป้าหมายที่ต้องถูกหลอกให้โปรเซสเซอร์ของโปรแกรมเหยื่อรันโค้ดแบบคาดเดาเท่านั้น ยิ่งไปกว่านั้น การจะอ่านผลของการประมวลผลแบบคาดเดาออกมาได้ก็ยังต้องมีแหล่งอ้างอิงเวลา (timing source) ที่ใช้ได้ด้วย
      วิธีป้องกันเรื่องนี้ แทบไม่มีเลยนอกจากแนวทางระดับเดือดทั้งมหาสมุทร[0] การประมวลผลแบบคาดเดามีคุณค่าต่อประสิทธิภาพมากเกินไป จนคอมพิวเตอร์ที่ไม่มีมันแทบใช้งานจริงไม่ได้เลย ถ้าอยากได้โปรเซสเซอร์ที่ไม่มีการประมวลผลแบบคาดเดาจริง ๆ ก็ไปซื้อ Pentium รุ่นแรกแบบเก่ามาใช้ได้
      วิธีบรรเทาที่ใช้งานได้จริงมีหลายแบบ แต่ขั้นต่ำที่สุดคือต้องรับประกัน การแยกโปรเซส ระหว่างโปรเซสเหยื่อที่มีความลับ กับผู้โจมตีที่เป็นไปได้ซึ่งสามารถมีอิทธิพลต่อการทำงานของเหยื่อได้
      Intel เคยถูกจับได้ว่าปล่อยให้มีการประมวลผลแบบคาดเดาข้าม ring ได้ จึงทำให้สามารถอ่านหน่วยความจำของเคอร์เนลหรือไฮเปอร์ไวเซอร์จาก user space ได้ ถ้าเป็น CPU ที่ออกแบบมาไม่แย่นัก สิ่งที่มักต้องกังวลหลัก ๆ จะเป็น HTML iframe
      แหล่งที่มาต่างกันไม่สามารถส่ง HTTP request ตามใจชอบหากันได้[1] แต่สามารถฝังอีกฝ่ายได้โดยไม่ต้องขออนุญาต[2] ตามธรรมเนียมแล้ว ข้อมูลนั้นจะถูกโหลดเข้าไปยังโปรเซสของผู้โจมตี และอาจรั่วไหลผ่านการโจมตีเชิงเวลาได้
      วิธีแก้ปัญหาในยุคแรกของเว็บไม่ใช่การแยก iframe ออกเป็นคนละโปรเซส แต่คือการถอด shared-memory multithreading ออกไปทั้งระบบ ถ้าทำให้ผู้โจมตีไม่มีฐานเวลาได้ ก็จะสำคัญน้อยลงว่าเหยื่อจะประมวลผลอะไรแบบคาดเดา แต่การทำแบบนั้นต้องตัด multithreading ออก เพราะไม่เช่นนั้นเธรดหนึ่งสามารถเขียนข้อมูลที่รู้ค่าอยู่แล้วซ้ำ ๆ ในลูปเพื่อสร้างนาฬิกาได้
      [0] https://hackaday.com/2013/08/02/the-mill-cpu-architecture/
      [1] อย่างน้อยก็ทำไม่ได้ถ้า origin ปลายทางไม่ได้อนุญาตผ่าน CORS
      [2] เช่น image hotlink หรือ iframe embed
    • จุดที่การประมวลผลแบบคาดเดาไปมีปฏิสัมพันธ์กับ การแปลงหน่วยความจำเสมือน และแคช คือส่วนที่ถูกนำไปใช้โจมตีได้
      ไม่ใช่ช่องโหว่ที่มีอยู่ในตัวการทำนายเอง
  • ในมุมของคนที่เพิ่งเริ่มศึกษา อ่านบทความแล้วก็ยังไม่ชัดเจนว่า ตัวทำนายสาขาแบบ 2-ahead คืออะไรกันแน่

    • เป็นแนวคิดเมื่อราว 30 ปีก่อน ดูเหมือนจะอ้างถึงงานวิจัยปี 1996 ฉบับนี้[0] เนื้อหาเกินระดับของผมไปหน่อย แต่ดูเหมือนจะช่วยแก้ปัญหาการทำนายสาขาที่เกิดจากการมีหลายยูนิตคำสั่งและความถี่สัญญาณนาฬิกาสูง
      ในยุค 90 มักจะมีอย่างใดอย่างหนึ่ง แต่โปรเซสเซอร์สมัยใหม่ดูเหมือนจะมีทั้งสองอย่าง
      บทคัดย่อของ “Multiple-block ahead branch predictors” อธิบายว่าเป็นวิธีทำนายบล็อกถัดไปอีกหนึ่งบล็อก โดยไม่ใช้ข้อมูลของบล็อกคำสั่งปัจจุบันในการทำนายที่อยู่ของบล็อกคำสั่งถัดไป แบบนี้ช่วยลดคอขวดของการดึงคำสั่งในโปรเซสเซอร์ “brainiac” ที่มีการ dispatch กว้าง และทำให้ทำนายที่อยู่ของบล็อกคำสั่งได้สองบล็อกต่อหนึ่งไซเคิลอย่างมีประสิทธิภาพ
      และสำหรับโปรเซสเซอร์ “speed demon” ก็สามารถทำให้กระบวนการทำนายสาขาเป็นแบบ pipeline ได้ เพื่อให้ได้ทั้งสัญญาณนาฬิกาที่สูงขึ้นหรือความแม่นยำที่ดีขึ้นจากโครงสร้างตัวทำนายที่ใหญ่กว่า ต่างจากวิธี multiple-predictor แบบเดิม ตัวทำนายสาขาแบบหลายบล็อกล่วงหน้าสามารถใช้วิธีทำนายสาขาแบบใดก็ได้
      [0] https://dl.acm.org/doi/10.1145/237090.237169
      เพิ่มเติมคือ ดูเหมือน eyegor จะโพสต์ลิงก์ไว้แล้ว แต่ตั้งใจจะบอกว่าอย่างน้อยควรอ่านบทคัดย่อ
    • เท่าที่เข้าใจ มันคือการทำนายเป้าหมายของ สาขาถัดไปอีกหนึ่งชั้น ไม่ใช่ของสาขาถัดไปทันที
      มันน่าจะยากกว่าการทำนายสาขาถัดไปมาก แต่ก็ทำให้สามารถดึงโค้ดมาเลี้ยง pipeline ที่ลึกกว่าได้เร็วขึ้นมาก
    • ต่อให้ไม่ใช่มือใหม่ก็งงเหมือนกัน บทความใช้เวลามากกับการอธิบายพื้นฐานมาก ๆ ของการทำนายสาขา แต่พอถึงตรง 2-ahead กลับข้ามคำอธิบายไป
    • ดูเหมือนว่าจะทำนาย 2 สาขา ต่อหนึ่งไซเคิล แทนที่จะเป็น 1 สาขา
      ดังนั้นแทนที่จะประเมินล่วงหน้าแค่ n+1 แบบตัวทำนายสาขาทั่วไป ก็อาจประเมินผลไปได้ถึง n+2 ผมยังไม่ค่อยเข้าใจว่ามันทำงานแบบนี้ได้อย่างไรโดยไม่ทำให้ L1 cache ปั่นป่วน
      ถ้าเป็นการมองล่วงหน้าเกิน n+1 จริง ก็น่าจะทำให้มีการขับข้อมูลออกจากแคชมากขึ้นมาก เลยคิดว่าน่าจะมีอะไรบางอย่างที่ผมยังพลาดไป
      ว่ากันว่า Zen 5 สามารถมองไปได้ไกลกว่า second taken branch ใน instruction stream และด้วยเหตุนี้จึงมีหน้าต่างการทำนาย 3 ชุดที่เป็นประโยชน์ต่อการสร้างคำสั่งสำหรับถอดรหัส
      งานวิจัยต้นฉบับเปิดให้อ่านได้สาธารณะ แต่ผมยังไม่ได้อ่านลึกมากนัก: https://dl.acm.org/doi/10.1145/237090.237169
    • ตัวทำนายสาขาทั่วไปจะเดาว่าก่อนที่สาขา เช่น if-else จะรันจริง มันจะไปทางไหน จากนั้น CPU ก็จะสามารถดึงและถอดรหัสคำสั่งไว้ล่วงหน้าได้
      แต่ละทิศทางของสาขาจะพาไปยังจุดเริ่มต้นของบล็อกคำสั่งใหม่ และคำสั่งสุดท้ายของบล็อกแบบนั้นก็มักเป็นอีกสาขาหนึ่ง
      พูดอีกอย่างคือ ตัวทำนายสาขาคืออุปกรณ์ที่เดาที่อยู่ของบล็อกถัดไป ตัวทำนายสาขาแบบ 2-ahead ก็ทำสิ่งเดียวกัน แต่ทำกับสองบล็อกถัดไป
      ตามถ้อยคำในงานวิจัย “ข้อมูลของบล็อกคำสั่งปัจจุบันถูกใช้เพื่อทำนายที่อยู่ของบล็อกที่อยู่ถัดจากบล็อกคำสั่งถัดไป”
      มันทำได้โดยไม่ต้องรอให้คำสั่งของบล็อกถัดไปถูกถอดรหัสก่อน ต่างจากตัวทำนายสาขาทั่วไป จึงสามารถป้อนงานให้ตัวถอดรหัสคำสั่งหลายตัวพร้อมกันได้
      สิ่งนี้มีประโยชน์เป็นพิเศษใน CPU สมัยใหม่ที่ตัวถอดรหัสคำสั่งกลายเป็นคอขวด เพราะมีตัวถอดรหัสเพียง 1 ตัวที่ถอดรหัสได้แค่ 1 คำสั่งต่อไซเคิล ย่อมตามไม่ทัน front-end แบบกว้างที่สามารถรันคำสั่งได้จำนวนมาก เช่น 4~6 คำสั่งต่อไซเคิล
  • อาจต้องมี branch hint มากขึ้น: https://github.com/ziglang/zig/issues/5177
    น่าจะทำแบบมี cold, warm, warmer แล้วให้ hot เป็นค่าเริ่มต้นจึงละไว้ได้หรือไม่ บางครั้งอาจกำหนดให้ทุกสาขาเป็น cold ยกเว้นแค่กรณีเดียวก็ได้

  • อาจเป็นความคิดที่ไม่ดี แต่ก็อยากรู้เหตุผล
    ถ้ามีการแตกแขนงแบบมีเงื่อนไข ทำไมถึงไม่ดึงและเตรียมคำสั่งของทั้งสองทางไว้ แล้วค่อยทิ้งฝั่งที่ผิดไป?
    อยากรู้ว่านี่เป็นงานที่ยากกว่ามาก หรือมีเหตุผลอื่นที่ทำให้มันไม่คุ้มค่า

    • นั่นเป็น กลยุทธ์ที่ด้อยกว่า
      ตัวทำนายการแตกแขนงแบบ TAGE สมัยใหม่ทำนายถูกได้มากกว่า 99% อยู่แล้วมาก ดังนั้นคำสั่งส่วนเพิ่มของอีกฝั่งจึงแทบจะถูกทิ้งเสมอ
      ที่แย่กว่านั้นคือฟรอนต์เอนด์ดึงคำสั่งล่วงหน้าไปไกลกว่าจุดที่แบ็กเอนด์จะยืนยันทิศทางจริงได้เป็นหลายสิบแขนง แล้วจะแก้กับแขนงถัดไปอย่างไร? จะถอดรหัสความเป็นไปได้ 4 ทาง แล้วต่อไป 8, 16, 32 ทางหรือ? สุดท้ายส่วนใหญ่ก็ต้องทิ้งอยู่ดี
      ถ้ามีฮาร์ดแวร์ที่ดึงหลายสตรีมคำสั่งแบบขนานได้ อย่าง Intel Gracemont/Goldmont/Skymont และ AMD Zen 5 กลยุทธ์ที่ดีกว่าคือสมมติว่าตัวทำนายการแตกแขนงถูก 100% แล้วตามแขนงหนึ่งไป จากนั้นก็ตามแขนงถัดไปต่อ
      Intel Skymont มีดีโคเดอร์ 3 ตัว และแต่ละตัวเป็นแบบ 3-wide จึงถอดรหัสเป้าหมายแขนงถัดไป 3 จุดพร้อมกัน Intel ถึงกับใส่แขนงปลอมเพื่อแยกบล็อกโค้ดขนาดใหญ่ ให้ดีโคเดอร์ทั้งสามถอดรหัสคนละส่วนของสตรีมคำสั่งที่กำลังจะมาถึงอยู่เสมอ จากนั้นจึงรวมสตรีมไมโครออปทั้ง 3 เข้าด้วยกัน เพื่อให้ Skymont รักษาแบนด์วิดท์การถอดรหัสที่มีผลจริงได้ 9 คำสั่งต่อไซเคิล
      การทำทั้งสองแขนงช่วยลดดีเลย์ได้เล็กน้อยเฉพาะในกรณีหายากที่ทำนายพลาดเท่านั้น ขณะที่การตามการทำนายถัดไปอีกสองสามครั้งในทิศทางเดียวกันทำให้ Intel และ AMD ใช้ดีโคเดอร์หลายตัวทำงานขนานกันได้ Intel จึงสร้าง 9-wide ได้ด้วยดีโคเดอร์ 3-wide ที่เรียบง่ายกว่า 3 ตัว และ AMD ก็สร้าง 8-wide ได้ด้วยดีโคเดอร์ 4-wide ที่เรียบง่ายกว่า 2 ตัว
    • กรณีที่ผลของแขนง เป็นแบบสุ่ม นั้นพบได้น้อย
      คอมไพเลอร์ รันไทม์ และ CPU มักเดาถูกว่าผลลัพธ์ไหนน่าจะเกิดขึ้นมากกว่า และโดยทั่วไปกลยุทธ์ที่ดีกว่าคือไม่ทำงานเพิ่มตั้งแต่แรก ดีกว่าเอาซิลิคอนและความร้อนไปเผื่อไว้กับคำตอบที่ผิดในกรณีที่อาจเดาผิด
      ดูเหมือนหลายคนยังไม่มีสัญชาตญาณว่าการทำนายการแตกแขนงแม่นยำได้ขนาดไหน แค่ดูโค้ดของตัวเองก็มักจะพอเห็นได้ทันทีว่า “โฟลว์ควบคุมส่วนใหญ่ไปทางนี้ และแขนงนี้มีไว้จัดการกรณียกเว้น”
      คอมไพเลอร์ยุคนี้ก็อนุมานเรื่องนี้ได้ดีพอสมควร และ CPU/JIT/รันไทม์ ก็สร้างฮิวริสติกที่น่าทึ่งได้เช่นกัน ถึงอย่างนั้นถ้ายังพลาด ก็ยังสามารถใส่ hint ไว้ในโค้ดเพื่อบอกทิศทางที่คาดหวังให้คอมไพเลอร์หรือส่วนอื่น ๆ ทราบได้
    • ผมไม่ได้ทำงานในสายนี้ เป็นแค่นักเล่น แต่ดูเหมือนว่าตัวทำนายการแตกแขนงดีเกินกว่าจะคุ้มกับการทำทั้งสองฝั่งมาตลอด
      reorder buffer ของคำสั่งใน CPU สมัยใหม่ลึกได้ถึงหลายร้อยคำสั่ง และถ้าในนั้นมี conditional jump แค่ 8 จุด เส้นทางที่โปรแกรมอาจไปได้ก็มี 256 ทางแล้ว
      ถ้าตัวทำนายการแตกแขนงมีโอกาสทายถูกทั้ง 8 จุดเกิน 50% ซึ่งในความเป็นจริงก็เกิน การทำงานเผื่อแบบ คูณ 256 ก็ไม่คุ้ม
    • นั่นเรียกว่า speculative execution และถ้าจำไม่ผิด CPU สมัยใหม่ทุกตัวก็ทำกันอยู่
      มันต้องใช้ซิลิคอนเพิ่มเพื่อเก็บสถานะจุลสถาปัตยกรรมให้มากขึ้น และต้องมี execution unit มากขึ้นเพื่อใช้เทคนิคนี้ให้เต็มที่ แต่สำหรับ CPU แบบ superscalar สิ่งเหล่านี้ก็จำเป็นอยู่แล้วเพื่อดึง instruction-level parallelism ออกจากโค้ดที่มีแขนงน้อย
      ที่เหลือก็เป็นงานน่าปวดหัวในการจัดการเรื่องซับซ้อนอย่าง aliasing และ interrupt แต่พวกวิศวกรฮาร์ดแวร์ก็เหมือนพ่อมดที่ทำเรื่องพวกนี้สำเร็จได้
      อย่างไรก็ตาม speculative execution เปิดช่องให้มีการใช้ประโยชน์จาก cache timing side channel เพื่อดึงข้อมูลจากข้อมูลที่โค้ดซึ่งถูก execute แบบ speculative เท่านั้น แต่ผลข้างเคียงเชิงสถาปัตยกรรมยังไม่ถูก commit เคยไปแตะต้องไว้ กล่าวคือ ข้อมูลอาจรั่วออกมาจากโค้ดที่ไม่ได้ถูกรัน “จริง ๆ” ก็ได้
      ซึ่งรวมถึงโค้ดที่ไม่ได้ถูกรันอย่างชัดเจนเพราะติดการตรวจสอบเงื่อนไข เช่น การตรวจสอบสิทธิ์
      ตัวอย่างการโจมตีที่คุ้นกันคือ Spectre: https://en.m.wikipedia.org/wiki/Spectre_(security_vulnerabil...
    • เมื่อหลายสิบปีก่อนก็ไปถึง ความแม่นยำ 90% แล้ว และชิปสมัยใหม่ทำได้ดีกว่านั้นมากขึ้นอยู่กับเวิร์กโหลด
      ดังนั้นโดยพื้นฐานแล้วมันไม่คุ้มเลย ทรัพยากรบนชิปพวกนั้นเอาไปใช้กับเธรดหรือคอร์อื่นจะดีกว่ามาก
  • ถ้าจะตัดสินว่านี่เป็นไอเดียที่ดีไหม ผมอยากเห็น ข้อมูลประสิทธิภาพ ก่อน ไม่มีแม้แต่ข้อมูลเรื่องโทษจากการทำนายการแตกแขนงพลาดของแนวทางนี้ด้วย
    ไม่ว่าอย่างไร สัญชาตญาณของแนวทางนี้ดูเหมือนจะอยู่ที่การดึงและถอดรหัสคำสั่งอย่างก้าวร้าว สำหรับคำสั่งที่อาจยังไม่มีอยู่ใน L1 instruction cache หรือ micro-op cache
    เรื่องนี้สำคัญกับ x86 และน่าจะรวมถึง RISC-V ด้วย เพราะทั้งคู่มีความยาวคำสั่งแปรผัน แค่ดูบล็อกใน instruction cache อย่างเดียว คอร์จะไม่รู้ว่าควรถอดรหัสคำสั่งภายในบล็อกนั้นอย่างไร สำหรับ ISA ทั้งสองแบบ การจะเริ่มถอดรหัสบล็อกจาก instruction cache ได้ ต้องรู้ PC ของคำสั่งอย่างน้อยหนึ่งคำสั่งก่อน
    ดังนั้นถ้ารู้ได้ว่าแอปพลิเคชันอาจกระโดดไปที่ไหนอีกสองบล็อกข้างหน้า ก็จะช่วยให้ดึงและถอดรหัสล่วงหน้าไปได้ไกลกว่าวิธีปัจจุบัน
    แนวทางนี้คล้าย instruction prefetching แต่ instruction prefetching ไม่ได้ให้ข้อมูลจุดเริ่มต้นแก่คอร์
    คอร์ ARM ประสิทธิภาพสูงอาจไม่เจอปัญหา “หาจุดเริ่มต้น” นี้ เพราะทุกคำสั่งยาว 32 บิต ดังนั้นจึงอาจทำขั้นตอนถอดรหัสแบบขนานได้แม้ไม่รู้จุดเริ่มต้น
    วิธีนี้น่าจะเป็นประโยชน์กับแอปพลิเคชันที่ติดข้อจำกัดฝั่งฟรอนต์เอนด์ เช่น cloud workload ที่บล็อกโค้ดร้อนกระจายอยู่ทั่วไบนารี ผมก็สงสัยเหมือนกันว่าสำหรับแอปพลิเคชันประเภทอื่นจะได้ประโยชน์ด้านประสิทธิภาพหรือกลับเสียประสิทธิภาพ

  • ผมก็ยังไม่เข้าใจเลยว่า ตัวทำนายการแตกแขนงแบบ 2-ahead คืออะไร

    • อาจจะดีกว่าถ้าเริ่มจากอ่านงานวิจัยเก่าที่ลิงก์ไว้ในบทความก่อน
      โดยทั่วไปงานวิจัยเก่ามักสมมติว่าผู้อ่านรู้เรื่องหัวข้อพวกนี้น้อยกว่ามาก เพราะในสมัยนั้นความรู้แบบนี้ยังเป็นเรื่องเฉพาะทางกว่านี้มาก
  • ตอนนี้สิ่งที่จำเป็นคือ แบนด์วิดท์หน่วยความจำ ช่องหน่วยความจำ 2 ช่องของซ็อกเก็ต AM5 สำหรับผู้บริโภคดูน้อยนิดเมื่อเทียบกับสมรรถนะการประมวลผลระดับนี้ โดยเฉพาะเมื่อเทียบกับ Apple Silicon รุ่นพื้นฐานด้วยซ้ำ
    ผมย้ายจากระบบ Zen แบบจัดเต็มไปใช้ M2 Max แล้วก็ประหลาดใจอีกครั้งว่าการเพิ่มขึ้นของแบนด์วิดท์หน่วยความจำช่วยเร่งงานข้อมูลเข้มข้นได้มากแค่ไหน แม้ในงานมัลติทาสก์ที่ค่อนข้างหนัก ท่อส่งหน่วยความจำอันคับแคบของระบบ Zen ก็มักจะตันอยู่บ่อยครั้ง

    • ในความเป็นจริงแล้ว มีแอปพลิเคชันน้อยมากที่ ติดคอขวดแบนด์วิดท์หน่วยความจำ แต่ยังเหมาะกับ CPU มากกว่า GPU
      เหตุผลที่ผู้คนหันไปมอง Apple Silicon โดยเฉพาะก็เพราะ LLM แม้ LLM จะเหมาะกับ GPU มากกว่า แต่ก็ต้องการ VRAM มาก และ NVIDIA ก็ตั้งราคา GPU ที่มี VRAM เยอะไว้สูงเกินเหตุ
      ถ้า AMD อยากสกัดกระแสของ NVIDIA จริง ๆ ก็ควรขาย GPU สำหรับผู้บริโภคที่มี VRAM 64~128GB
    • AM5 มี ช่องหน่วยความจำ 4 ช่อง อยู่จริง เพราะ DDR5 เพิ่มจำนวนช่องสัญญาณขึ้นเป็นสองเท่า