Parallel Systems · บทที่ 2 จาก 9

Parallelism Inside a CPU

ก่อนจะพูดถึงการขนานข้ามหลายเครื่อง ต้องรู้ก่อนว่าซีพียูตัวเดียวก็ "ขนาน" อยู่แล้วในระดับคำสั่ง (Instruction-Level Parallelism) — และทำไมแนวทางนี้ถึงไปต่อได้จำกัด

📚
สังเคราะห์จากเอกสาร "Parallelism in CPU" (2110732 Parallel Computing, ราว 2008-2012) — เนื้อหาเรื่อง pipelining, hazards และ superscalar ยังเป็นพื้นฐานสถาปัตยกรรมซีพียูที่ไม่เปลี่ยน แต่บริบทการใช้งาน (security, SMT) พัฒนาไปมากตั้งแต่นั้น

1Pipelining: สายพานการผลิตคำสั่ง

แนวคิด
แบ่งการประมวลผลคำสั่งหนึ่งคำสั่งออกเป็นขั้นตอนย่อย (stage) แล้วให้แต่ละขั้นตอนทำงานกับคำสั่งคนละตัวพร้อมกัน คล้ายสายพานการผลิตในโรงงาน — ขั้นตอนคลาสสิก 5 stage: Fetch → Decode → Operand → Execute → Store
             1   2   3   4   5   6   7   8   9
Inst 1       F   D   O   E   S
Inst 2           F   D   O   E   S
Inst 3               F   D   O   E   S
Inst 4                   F   D   O   E   S
ทำไม Pipeline ถึงเร็วขึ้น
สมมติ clock speed = 500 MHz (1 clock = 2 ns), รันคำสั่ง 1000 คำสั่ง แบบ 5-stage pipeline
ไม่มี pipeline: 2 ns/cycle × 5 cycles/instr × 1000 instr = 10,000 ns มี pipeline: 2 ns/cycle × (1 cycle/instr × 1000 instr + 4 cycles เติม pipeline) = 2,008 ns
เร็วขึ้นเกือบ 5 เท่า (เข้าใกล้จำนวน stage) เมื่อ pipeline "เต็ม" และไม่มีการติดขัด — นี่คือ ILP ในรูปแบบที่พื้นฐานที่สุด

2Hazard: สิ่งที่ทำให้ Pipeline สะดุด

ประเภทสาเหตุ
Structural Hazardฮาร์ดแวร์ไม่พอให้สอง stage ทำงานพร้อมกัน เช่น มี memory port เดียวแต่ทั้ง Fetch และ Store ต้องใช้ในรอบเดียวกัน
Data Hazardคำสั่งหนึ่งต้องใช้ผลลัพธ์ของคำสั่งก่อนหน้าที่ยังทำงานไม่เสร็จ แบ่งเป็น 3 แบบ: RAW (Read After Write — อ่านค่าที่ยังไม่ถูกเขียนจริง) WAW (Write After Write — เขียนสลับลำดับผิด) WAR (Write After Read — เขียนทับก่อนที่คำสั่งก่อนหน้าจะอ่านค่าเดิมเสร็จ)
Control Hazardเกิดจากคำสั่งแตกกิ่ง (branch) — Pipeline ไม่รู้ว่าจะ Fetch คำสั่งไหนต่อ จนกว่าจะรู้ผลของเงื่อนไข ซึ่งกว่าจะรู้ก็ผ่านไปหลาย stage แล้ว
Branch Prediction — ทางแก้ Control Hazard
แทนที่จะหยุดรอผลของเงื่อนไข ซีพียูเดาว่าจะไปทางไหน แล้ว Fetch คำสั่งต่อจากทางที่เดาไว้ทันที — ถ้าเดาถูก ไม่เสียเวลาเลย ถ้าเดาผิด ต้องยกเลิกงานที่ทำไปแล้วทั้งหมดใน pipeline (flush) แล้วเริ่มใหม่ — ยิ่ง pipeline ยาว ยิ่งเสียเวลามากเมื่อเดาผิด

3Superscalar: ก้าวข้าม "หนึ่งคำสั่งต่อหนึ่งรอบสัญญาณนาฬิกา"

แนวคิด
ซีพียูทั่วไปทำได้แค่ 1 คำสั่งต่อ 1 cycle ต่อ pipeline เดียว — Superscalar เพิ่มหน่วยประมวลผลหลายชุดขนานกันภายในซีพียูตัวเดียว ทำให้ issue คำสั่งได้มากกว่า 1 คำสั่งต่อ cycle
เทคนิคที่ใช้ร่วมทำหน้าที่อะไร
Dynamic Schedulingซีพียูจัดลำดับการรันคำสั่งใหม่ ณ runtime (ไม่รันตามลำดับในโค้ดเป๊ะ) เพื่อเลี่ยง hazard และใช้หน่วยประมวลผลให้เต็มประสิทธิภาพ
Register Renamingแก้ WAW/WAR hazard ด้วยการให้แต่ละคำสั่งเขียนผลลัพธ์ลง physical register ใหม่จาก free pool แทนที่จะใช้ register เดิมซ้ำ — ทำให้คำสั่งที่ "ดูเหมือน" ขัดแย้งกันแท้จริงแล้วทำงานอิสระต่อกันได้
Out-of-order Executionคำสั่งที่พร้อมทำงานก่อน (ข้อมูลครบแล้ว) ถูกรันก่อน แม้จะมาทีหลังในโค้ดต้นฉบับ — ผลลัพธ์สุดท้าย "ดูเหมือน" รันตามลำดับเดิมเสมอ (in-order retirement)

Multimedia Extension: MMX, 3DNow! — บทเรียนประวัติศาสตร์

แนวคิดดั้งเดิม
ราวปลายยุค 1990 พบว่างานมัลติมีเดีย (ภาพ, เสียง, วิดีโอ) มักทำ operation แบบเดียวกันซ้ำ ๆ กับข้อมูลจำนวนมาก — MMX (Intel) และ 3DNow! (AMD) เพิ่มชุดคำสั่งพิเศษที่ทำ operation เดียวกันกับข้อมูลหลายค่าพร้อมกันในคำสั่งเดียว (SIMD ระดับคำสั่ง)
เชื่อมโยงกับปัจจุบัน
แนวคิด MMX/3DNow! คือจุดเริ่มต้นของSIMD instruction setที่วิวัฒนาการต่อมาเป็น SSE, AVX, AVX-512 ในซีพียูปัจจุบัน และเป็นรากฐานเดียวกับที่ทำให้ GPU (ซึ่งเป็น SIMD/SIMT scale ใหญ่กว่ามาก) เหมาะกับงาน AI/deep learning ในทุกวันนี้ — ดูรายละเอียดสถาปัตยกรรม SIMD เต็มรูปแบบในบทที่ 4

4ข้อจำกัดของ ILP ในเครื่องปัจจุบัน และต้นทุนที่เอกสารเดิมยังไม่กล่าวถึง

แล้ว · แนวโน้มที่เอกสารต้นฉบับคาดการณ์ไว้
ตอนนี้ · สิ่งที่เกิดขึ้นจริง
เอกสารต้นฉบับระบุ "Trends in ILP" ว่าจะมีการเพิ่ม logic ให้ superscalar ซับซ้อนขึ้นเรื่อย ๆ และเริ่มพูดถึง "Chip Multiprocessor (CMP)" เป็นแนวโน้มใหม่ที่กำลังเกิดขึ้น
CMP (คือ multicore ที่เราเรียกกันทุกวันนี้) กลายเป็นมาตรฐานอุตสาหกรรมทั้งหมด — เพราะการเพิ่มความซับซ้อนของ superscalar ตัวเดียวให้เร็วขึ้นต่อไปนั้น "ผลตอบแทนลดลงเรื่อย ๆ" (diminishing returns) ในขณะที่กินพลังงานเพิ่มขึ้นแบบไม่เป็นเชิงเส้น (ตรงกับ power wall ในบทที่ 1) อุตสาหกรรมจึงหยุดเพิ่มความซับซ้อนของ core เดียว แล้วเพิ่มจำนวน core ที่เรียบง่ายกว่าแทน
ต้นทุนที่เอกสารปี 2008-2012 ยังไม่รู้จัก: Speculative Execution Vulnerabilities
Branch prediction และ out-of-order execution ทำให้ซีพียูต้อง "เดาและรันล่วงหน้า" ก่อนรู้ผลจริง — ปี 2018 นักวิจัยค้นพบช่องโหว่ระดับสถาปัตยกรรม Spectre และ Meltdown ที่ใช้ประโยชน์จากพฤติกรรมนี้ในการอ่านข้อมูลที่ควรเข้าถึงไม่ได้ ผ่านการวัดเวลาที่ cache ใช้ (side-channel attack) — เป็นตัวอย่างชัดเจนว่าเทคนิคเพิ่มประสิทธิภาพ (ที่บทนี้สอน) กับความปลอดภัยเป็นสองแกนที่บางครั้งขัดแย้งกัน ซีพียูรุ่นใหม่ต้องแลก performance บางส่วนเพื่อ patch ช่องโหว่เหล่านี้
Simultaneous Multithreading (SMT / Hyper-Threading)
เทคนิคที่ต่อยอดจาก superscalar โดยตรง — ให้ 1 core ทางกายภาพ "แสร้งทำเป็น" 2 core เชิงตรรกะ (logical core) เพื่อใช้หน่วยประมวลผลที่ว่างจาก thread หนึ่งให้อีก thread หนึ่งใช้งานได้ในเวลาเดียวกัน — ใช้ประโยชน์จาก idle execution unit ที่เกิดจาก data hazard ของ thread แรก โดยไม่ต้องเพิ่ม core ทางกายภาพจริง
คำถามซ้อมสอบ
  1. อธิบายว่าทำไม pipeline 5 stage ที่ทำงานเต็มประสิทธิภาพจึงเร็วขึ้นเกือบ 5 เท่า ไม่ใช่ 5 เท่าเป๊ะ พร้อมคำนวณตัวอย่างของตัวเอง
  2. แยกแยะ RAW, WAW, WAR hazard พร้อมยกตัวอย่างโค้ดของแต่ละแบบ
  3. อธิบายว่า register renaming แก้ WAW และ WAR hazard ได้อย่างไร
  4. อธิบายว่าทำไมอุตสาหกรรมซีพียูจึงเปลี่ยนทิศทางจาก "เพิ่มความซับซ้อนของ superscalar core เดียว" ไปเป็น "เพิ่มจำนวน core" ตั้งแต่กลางทศวรรษ 2000
  5. อธิบายว่า Spectre/Meltdown ใช้ประโยชน์จากกลไกใดของซีพียูสมัยใหม่ และทำไมจึงเป็นตัวอย่างของการแลก performance กับ security

5หนึ่งคำสั่งไม่ได้เกิดขึ้นในหนึ่งจังหวะ

เวลาเราอ่าน assembly หนึ่งบรรทัด ดูเหมือน CPU หยิบคำสั่งมาทำแล้วจบ ความจริงคำสั่งต้องผ่านหลายขั้น เช่น fetch, decode, อ่าน operand, execute, เข้าถึง memory และเขียนผลกลับ หากรอให้คำสั่งหนึ่งผ่านครบทุกขั้นก่อนเริ่มคำสั่งถัดไป วงจรแต่ละส่วนจะว่างอยู่มาก

สายพานซักรถ
รถคันแรกกำลังฉีดน้ำ ไม่จำเป็นต้องรอให้เช็ดแห้งก่อนรถคันที่สองเข้าจุดฉีดน้ำ Pipelining ทำให้หลายคำสั่งอยู่คนละ stage พร้อมกัน จึงเพิ่ม throughput แม้ latency ของคำสั่งหนึ่งอาจไม่ได้สั้นลง
Stage แบบง่ายหน้าที่สิ่งที่อาจทำให้รอ
IFดึง instruction จาก cacheI-cache miss หรือทายเส้นทาง branch ผิด
IDถอดรหัสและระบุ operandคำสั่งซับซ้อนหรือทรัพยากรถอดรหัสไม่พอ
EXคำนวณด้วย ALU/FPUOperand ยังไม่พร้อมหรือ execution unit เต็ม
MEMอ่านหรือเขียนข้อมูลCache miss, TLB miss และ memory latency
WBเขียนผลกลับ registerช่องทางเขียนผลชนกัน

6Latency กับ Throughput ของ Pipeline

Pipeline ลึกขึ้นแบ่งงานแต่ละ stage ให้สั้น จึงอาจเพิ่ม clock frequency และรับคำสั่งใหม่ได้ถี่ขึ้น แต่คำสั่งหนึ่งต้องผ่าน stage มากขึ้น และเมื่อ branch ผิดต้องทิ้งงานใน pipeline มากกว่าเดิม จึงไม่มีเหตุผลว่าลึกที่สุดจะดีที่สุดเสมอ

อุดมคติ
เวลาของ n คำสั่ง ≈ (k + n − 1) · t
เมื่อ k คือจำนวน stage และ t คือเวลาต่อ stage หลัง pipeline เต็ม อาจเสร็จหนึ่งคำสั่งต่อ cycle แต่ช่วงเติม pipeline และระบายท้ายงานยังมีต้นทุน

คำว่า one instruction per cycle จึงหมายถึง throughput ในสภาวะต่อเนื่อง ไม่ได้หมายความว่าคำสั่งแต่ละตัวใช้เวลาเพียง cycle เดียว ความต่างนี้สำคัญมากเมื่อวิเคราะห์ latency chain

7Data Hazard: เมื่อคำสั่งหลังต้องรอผลคำสั่งก่อน

Hazardความหมายตัวอย่าง
RAW — Read After Writeคำสั่งหลังต้องอ่านค่าที่คำสั่งก่อนยังเขียนไม่เสร็จ เป็น dependency จริงr3=r1+r2 แล้ว r4=r3*2
WAR — Write After Readคำสั่งหลังจะเขียน register ก่อนคำสั่งก่อนอ่าน เป็นชื่อชนกัน ไม่ใช่ data flow จริงพบใน out-of-order execution
WAW — Write After Writeสองคำสั่งเขียนชื่อเดียวกัน ต้องรักษาลำดับผลสุดท้ายเขียน register เดิมจากสอง instruction

Forwarding ส่งผลจาก execution unit ไปยังคำสั่งถัดไปโดยไม่รอเขียนกลับ register file ลด stall ของ RAW บางชนิด แต่ load-use dependency มักยังต้องรอ เพราะข้อมูลจาก cache มาถึงช้ากว่า ALU result

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

8Control Hazard และ Branch Prediction

เมื่อพบ branch CPU ยังไม่รู้ instruction ถัดไปจนคำนวณเงื่อนไขเสร็จ หากหยุดรอทุกครั้ง throughput จะตกมาก CPU จึงทายเส้นทางและ fetch ต่อแบบ speculative หากทายถูกงานที่ทำล่วงหน้าใช้ได้ หากผิดต้อง flush และเริ่มใหม่จากเส้นทางจริง

ตัวทำนายไม่ได้เดาสุ่ม

Branch ที่คาดเดาได้ เช่น loop ที่วนซ้ำจำนวนมาก มักมี accuracy สูง ส่วนข้อมูลสุ่มที่เลือกเส้นทางคนละครึ่งคาดยาก การเรียงข้อมูลหรือใช้ branchless transformation บางครั้งจึงช่วยได้ แต่ต้องวัด เพราะคำสั่งเพิ่มและ vectorization อาจเปลี่ยน trade-off

9Superscalar: ออกคำสั่งได้มากกว่าหนึ่งคำสั่งต่อ Cycle

Pipeline แบบ scalar รับคำสั่งใหม่ได้หนึ่งคำสั่งต่อ cycle ส่วน superscalar CPU มีหลาย execution units และพยายาม issue หลายคำสั่งใน cycle เดียว ถ้าคำสั่งเหล่านั้น independent และ operand พร้อม

ความกว้างของ frontend, จำนวน decoder, reservation station, execution port และ bandwidth การเขียนผลล้วนเป็นข้อจำกัด การบอกว่า CPU เป็น 4-wide จึงไม่ได้แปลว่าทุกโปรแกรมได้ 4 instructions per cycle หากโค้ดมี dependency chain หรือ cache miss ยาว execution units ก็ยังว่าง

ด่านเก็บเงินหลายช่อง
เพิ่มช่องด่านช่วยได้เมื่อมีรถหลายคันอิสระต่อกัน แต่ถ้ารถทุกคันผูกเป็นขบวนและคันหลังต้องรอคันหน้า ช่องที่เหลือไม่มีงานให้รับ ความกว้างของ CPU มีค่าเมื่อโปรแกรมมี Instruction-Level Parallelism ให้ดึงออกมา

10Out-of-Order Execution: เปลี่ยนลำดับทำ แต่ไม่เปลี่ยนความหมาย

ถ้าคำสั่งหนึ่งรอข้อมูลจาก memory คำสั่งถัดไปที่ไม่เกี่ยวข้องอาจทำก่อนได้ CPU จึง decode ตามลำดับ แต่ schedule execution ตามความพร้อม แล้ว commit ผลกลับตามลำดับโปรแกรมเพื่อรักษา precise state

  1. Fetch/Decode รับ instruction ตามเส้นทางที่คาดไว้
  2. Rename เปลี่ยน architectural register เป็น physical register เพื่อตัด WAR/WAW false dependency
  3. Dispatch ส่ง instruction เข้า scheduler หรือ reservation station
  4. Issue/Execute เลือกคำสั่งที่ operand พร้อมไปยัง execution unit
  5. Retire ยืนยันผลตามลำดับผ่าน reorder buffer

Register renaming เปรียบเหมือนนักศึกษาสองคนมีชื่อเล่น “เอ” เหมือนกัน ระบบจึงออกหมายเลขประจำตัวใหม่ให้ ไม่ต้องบังคับให้ทั้งสองทำงานต่อคิวเพียงเพราะชื่อชนกัน ส่วน RAW เป็นความสัมพันธ์จริงที่เปลี่ยนชื่อแล้วก็ยังต้องรอ

11Reorder Buffer และ Precise Exception

เมื่อ execution เกิดนอกลำดับ หากคำสั่งเก่าพบ exception CPU ต้องทำให้ระบบดูเหมือนคำสั่งก่อนหน้าสำเร็จแล้ว ส่วนคำสั่งนั้นและคำสั่งหลังยังไม่เกิด Reorder Buffer เก็บผลชั่วคราวและ retire ตามลำดับ จึงย้อน speculative work ที่ยังไม่ commit ได้

กลไกนี้ทำให้ฮาร์ดแวร์ภายในซับซ้อน แต่ software ยังเห็น sequential semantics ตาม Instruction Set Architecture เป็นตัวอย่างของ abstraction ที่ซ่อน parallel execution ไว้ใต้โปรแกรมแบบลำดับ

12Speculation: ทำงานก่อนรู้ว่าจำเป็นหรือไม่

Branch prediction บอกเส้นทางที่น่าจะถูก CPU จึง execute instruction ต่อไปแบบ speculative ผลยังไม่เปิดเผยเชิงสถาปัตยกรรมจนกว่าจะยืนยันเส้นทาง หากทายผิดจึงทิ้งผลได้

แต่ผลข้างเคียงระดับ microarchitecture เช่น cache line ที่ถูกโหลด อาจยังคงอยู่ นี่เป็นรากของช่องโหว่อย่าง Spectre ซึ่งผู้โจมตีชักนำให้ speculative execution แตะข้อมูลลับ แล้วอ่านร่องรอยผ่าน timing แม้ผล instruction ถูกยกเลิกแล้ว

ถูกต้องตามโปรแกรม แต่รั่วผ่านเวลา
ISA อาจบอกว่าคำสั่งบนเส้นทางผิดไม่เคยเกิด แต่ cache จำว่ามันเคยถูกแตะ ความปลอดภัยจึงต้องมอง state ที่ software มองไม่เห็นโดยตรงด้วย Performance optimization สามารถเปลี่ยน threat model ได้

13Spectre กับ Meltdown ต่างกันตรงไหน

กลุ่มช่องโหว่แนวคิดโดยย่อผลของการป้องกัน
Spectreหลอก branch prediction/speculation ให้เข้าถึงข้อมูลนอกเส้นทางที่ควร แล้วใช้ side channel อ่านร่องรอยใช้ fence, retpoline, compiler/hardware mitigation และปรับ isolation
Meltdownอาศัย transient execution ผ่านการตรวจ privilege บางรูปแบบ แล้วอ่านร่องรอยผ่าน cacheแยก page table เช่น KPTI และใช้ hardware รุ่นที่แก้พฤติกรรม

รายละเอียด variant มีมากและเปลี่ยนตาม microarchitecture ประเด็นในวิชานี้คือ optimization ที่ซ่อนอยู่ไม่ใช่ของฟรี มันสร้าง state, timing และช่องทางสังเกตใหม่ซึ่ง software security model เดิมอาจไม่ครอบคลุม

14SIMD และ Vectorization: หนึ่งคำสั่งกับข้อมูลหลายตัว

ILP ทำหลาย instruction อิสระพร้อมกัน ส่วน SIMD ใช้ instruction เดียวทำ operation เดียวกับข้อมูลหลาย element เช่น บวกเลข 8 ค่าใน vector register พร้อมกัน เหมาะกับ loop ที่แต่ละ iteration เป็นอิสระและ control flow คล้ายกัน

// scalar concept
for (i = 0; i < n; i++) c[i] = a[i] + b[i];

// vector concept: โหลด a,b หลายค่าต่อครั้ง แล้วบวกเป็นชุด

Compiler อาจ auto-vectorize ได้เมื่อพิสูจน์ว่าไม่มี alias หรือ loop-carried dependency หาก pointer อาจชี้ทับกัน หรือมี branch ซับซ้อน compiler อาจระวังจนไม่ vectorize Programmer จึงควรอ่าน optimization report และวัดผล ไม่ใช่เดาจาก syntax

15SIMD, SIMT และ GPU

GPU ใช้แนวคิดคล้าย SIMD แต่จัดโปรแกรมเป็น threads จำนวนมากภายใต้โมเดล SIMT Threads ใน warp หรือ wavefront เดิน instruction เดียวกัน ถ้า branch ไปคนละทางต้อง execute แยกเส้นทางและ mask threads บางส่วน เรียกว่า divergence

ขบวนทัศนศึกษา
นักเรียนทั้งกลุ่มเดินตามไกด์คนเดียวได้เร็ว หากครึ่งหนึ่งจะแวะร้านกาแฟและอีกครึ่งไปพิพิธภัณฑ์ ไกด์ต้องพาไปทีละกลุ่มและให้คนที่ไม่เกี่ยวรอ GPU จึงชอบงาน data-parallel ที่ control flow สม่ำเสมอ

16SMT: ใช้หลาย Thread เติมช่องว่างของ Core

Simultaneous Multithreading เช่น Intel Hyper-Threading ให้ hardware thread หลายตัวแชร์ execution resources ของ core เดียว เมื่อ thread หนึ่งรอ cache miss อีก thread อาจใช้ execution slot ที่ว่าง ช่วย utilization แต่ไม่ได้เพิ่ม ALU, cache หรือ memory bandwidth เป็นสองเท่า

งานที่ใช้ resource คนละชนิดอาจอยู่ร่วมกันได้ดี ส่วนสอง thread ที่แย่ง execution port หรือ memory bandwidth เดียวกันอาจแทบไม่เร็วขึ้น และอาจช้าลงจาก contention ดังนั้น logical CPU ไม่ควรถูกนับเท่ากับ physical core โดยไม่วัด workload จริง

17Memory-Level Parallelism

Out-of-order core สามารถมี cache miss หลายรายการ outstanding พร้อมกัน เพื่อซ้อน latency ของ memory access หาก access independent กัน แต่ dependency chain แบบ pointer chasing เช่น linked list ทำให้ request ถัดไปเริ่มไม่ได้จนค่าปัจจุบันกลับมา จึงใช้ bandwidth ไม่เต็มแม้ memory system รองรับมาก

รูปแบบข้อมูลพฤติกรรมผลต่อ CPU
Array ต่อเนื่องPrefetch ได้และสร้างหลาย request ล่วงหน้าใช้ cache line และ bandwidth ได้ดี
Linked structureAddress ถัดไปขึ้นกับค่าที่เพิ่งอ่านLatency-bound และ MLP ต่ำ
Random gatherหลาย address อิสระแต่กระจายอาจซ้อน request ได้ แต่ cache locality ต่ำ

18Compiler ช่วยดึง ILP อย่างไร

Optimization อาจเพิ่ม code size จน instruction cache แย่ลง หรือ unroll มากจนใช้ register เกินและเกิด spill การเปิด optimization สูงสุดจึงไม่รับประกันว่าจะดีที่สุดทุกโปรแกรม

19อ่าน Performance Counter ให้ฮาร์ดแวร์เล่าเรื่อง

เวลาอย่างเดียวบอกว่าโปรแกรมช้า แต่ไม่บอกว่าช้าเพราะอะไร Hardware counter ช่วยวัด cycles, instructions retired, branch miss, cache miss, stalled cycles และ memory bandwidth

ตัวชี้วัดเบื้องต้น
IPC = Instructions Retired / CPU Cycles
CPI = CPU Cycles / Instructions Retired
IPC ต่ำอาจมาจาก dependency, branch miss หรือ memory stall ต้องอ่าน counter อื่นประกอบ ไม่ควรตีความตัวเลขเดียว
อาการหลักฐานที่ควรดูเพิ่มสมมติฐานเบื้องต้น
IPC ต่ำ + cache miss สูงMemory bandwidth และ latency stallMemory-bound หรือ locality ไม่ดี
Branch miss สูงตำแหน่ง branch และ input distributionControl flow คาดเดายาก
IPC ต่ำแต่ miss ต่ำPort utilization และ dependency chainExecution-bound หรือ ILP ต่ำ

20กรณีศึกษา: ผลรวมสะสมที่ขนานยากกว่าผลรวมธรรมดา

การ sum array ธรรมดาแบ่งช่วงให้หลาย accumulator แล้วรวมท้ายได้ แต่ prefix sum ต้องให้ผลตำแหน่ง i รวมค่าก่อนหน้าทั้งหมด ดูเหมือนเป็น dependency chain ยาว หากเปลี่ยน algorithm เป็น tree-based scan เราสามารถทำ upsweep และ downsweep เป็นรอบ ๆ เปิด parallelism โดยเพิ่มโครงสร้างการคำนวณ

ตัวอย่างนี้ชี้ว่าฮาร์ดแวร์ดึง ILP จากโปรแกรมได้เพียงขอบเขตหนึ่ง หาก dependency อยู่ใน algorithm จริง ต้องเปลี่ยน algorithm ไม่ใช่หวังให้ out-of-order execution แก้แทนทั้งหมด

21แล็บที่แนะนำ

แล็บ 1: Dependency Chain
เปรียบเทียบ loop ที่ใช้ accumulator ตัวเดียวกับ loop ที่ใช้ accumulator 4–8 ตัวอิสระ วัด IPC และ runtime อธิบายว่าการแตก chain เปิด ILP อย่างไร แล้วตรวจว่าจุดใด memory bandwidth กลายเป็นคอขวดแทน
แล็บ 2: Branch Prediction
นับค่าที่ผ่านเงื่อนไขใน array แบบ sorted และ shuffled ด้วยโค้ดเดียวกัน วัด branch miss และเวลา จากนั้นทดลอง branchless/vectorized version เพื่อแยกผลของ predictor กับ memory access
แล็บ 3: Cache และ Stride
เดิน array ด้วย stride 1, 2, 4, 16 และค่าที่ใหญ่กว่าหนึ่ง cache line วัด bandwidth และ cache/TLB miss แล้วอธิบาย spatial locality จากกราฟ

Checklist เวลาโค้ดบน CPU ช้ากว่าที่คาด

  • ดู IPC/CPI พร้อม instructions retired ไม่ใช้เวลาอย่างเดียว
  • แยกว่า frontend ส่งคำสั่งไม่ทัน หรือ backend รอ execution/memory
  • ตรวจ branch miss และดูว่า input distribution ทำให้ branch คาดเดายากหรือไม่
  • ตรวจ dependency chain โดยเฉพาะ accumulator และ pointer chasing
  • ตรวจ compiler optimization report ว่า loop vectorize ได้หรือไม่ และติดเหตุผลใด
  • วัด cache/TLB miss, memory bandwidth และ stride ของการเข้าข้อมูล
  • เปรียบเทียบ physical cores กับ SMT threads แยกกัน
  • ตรวจ code size และ instruction-cache effect หลัง unroll หรือ inline

การ optimize ควรเริ่มจาก bottleneck ที่มีหลักฐาน หาก branch miss ต่ำ การเขียน branchless code อาจเพิ่ม instruction โดยไม่ช่วย หาก memory bandwidth อิ่ม การ unroll เพิ่มก็เพียงทำให้หลายคำสั่งมารอ memory พร้อมกันมากขึ้น

จาก ILP ไปสู่ Thread-Level Parallelism

เมื่อ dependency และ instruction window จำกัดงานที่ core หนึ่งมองเห็น CPU ไม่สามารถดึง ILP เพิ่มได้ไม่สิ้นสุด การมีหลาย cores จึงเปิด instruction streams คนละสายให้ทำงานพร้อมกัน แต่แลกกับโจทย์ใหม่ ได้แก่การแบ่งข้อมูล cache coherence, synchronization และ load balance สิ่งที่ฮาร์ดแวร์เคยซ่อนให้ภายใน core จะเริ่มต้องปรากฏในโครงโปรแกรมมากขึ้น

22สรุปและขั้นตอนถัดไป

ภายใน CPU หนึ่ง core มี parallelism หลายชั้น ทั้ง pipelining, superscalar issue, out-of-order execution, speculation, SIMD, SMT และ memory-level parallelism ฮาร์ดแวร์พยายามทำให้โปรแกรมแบบลำดับดูเหมือนทำทีละคำสั่ง ทั้งที่ภายในมีคำสั่งจำนวนมากกำลังเดินทางพร้อมกัน

แต่ฮาร์ดแวร์สร้าง parallelism จากอากาศไม่ได้ Dependency, branch ที่คาดยาก และ memory latency ยังเป็นเพดาน บทถัดไปจึงถอยออกมาจากวงจร CPU แล้วจัดหมวดว่า parallelism ในระดับ algorithm มีรูปแบบใดบ้าง เราจะได้เลือกว่าจะกระจายข้อมูล แยกหน้าที่ หรือต่อเป็น pipeline ก่อนตัดสินใจใช้เครื่องมือครับ