Amdahl’s Law
มีเพดานทางทฤษฎีว่าการทำงานแบบขนานจะเร่งความเร็วได้แค่ไหน
ใจความสำคัญ
หัวข้อที่มีชื่อว่า “ใจความสำคัญ”Amdahl’s Law ตั้งชื่อตาม Gene Amdahl วิศวกรคอมพิวเตอร์ผู้ออกแบบเมน frame ของ IBM ที่เสนอแนวคิดนี้ในปี 1967 ที่งาน AFIPS Spring Joint Computer Conference เพื่อโต้แย้งข้อถกเถียงในยุคนั้นที่มองว่าการเพิ่มจำนวน processor จะทำให้ระบบเร็วขึ้นแบบไม่มีขีดจำกัด บริบทของยุคนั้นคือการถกเถียงกันในวงการว่าระบบ massively-parallel อย่าง ILLIAC IV จะเป็นอนาคตของการประมวลผลหรือไม่ Amdahl ใช้สูตรทางคณิตศาสตร์ง่าย ๆ นี้เพื่อชี้ให้เห็นว่าฮาร์ดแวร์ที่ทำขนานได้มหาศาลก็ยังมีขีดจำกัดจากส่วนของโปรแกรมที่ยังต้องทำงานแบบ serial อยู่ดี
กฎนี้เขียนเป็นสูตรได้ว่า
Speedup(N) = 1 / ( (1 − P) + P/N )โดยที่ P คือสัดส่วนของงานทั้งหมดที่สามารถทำแบบขนานได้ (parallelizable) (1 − P) คือสัดส่วนที่ต้องทำแบบ serial เท่านั้น และ N คือจำนวน processor หรือหน่วยประมวลผลที่ใช้ทำงานส่วนที่ขนานได้
แก่นของกฎคือ: ไม่ว่าจะทุ่ม N มากเท่าไหร่ ผลลัพธ์ของสูตรก็ไม่มีวันเกิน 1 / (1 − P) เพราะเมื่อ N เข้าใกล้อนันต์ พจน์ P/N จะเข้าใกล้ศูนย์ เหลือแต่ส่วน serial ที่ตรึงเพดานไว้ตลอด นี่คือเหตุผลที่ “คอขวด” (bottleneck) ที่ทำขนานไม่ได้ ไม่ว่าจะเล็กแค่ไหน ก็เป็นตัวกำหนดผลลัพธ์สุดท้ายของทั้งระบบ
ความหมายและนัยยะ
หัวข้อที่มีชื่อว่า “ความหมายและนัยยะ”ตัวเลขที่ทำให้เห็นภาพชัดคือการลองแทนค่า สมมติมีงานที่ 10% ของเวลาทั้งหมดถูกเร่งความเร็วได้ถึง 10,000 เท่า (แทบจะเป็นศูนย์วินาที) ส่วนที่เหลืออีก 90% ยังคงทำงานแบบ serial เหมือนเดิม ผลลัพธ์สุดท้ายคือระบบเร็วขึ้นเพียงประมาณ 11% เท่านั้น — ไม่ใช่ 10,000 เท่าอย่างที่หลายคนคาดหวัง ในทางกลับกัน ถ้าเพิ่ม speed ให้ 99% ของงานด้วยตัวเร่ง 100 เท่า ระบบโดยรวมจะเร็วขึ้นได้สูงสุดประมาณ 50 เท่า เพราะ 1% ที่เหลือกลายเป็นเพดานใหม่
นัยยะสำคัญมีสามข้อ
- ผลตอบแทนลดลงเรื่อย ๆ (diminishing returns) — การเพิ่ม processor หรือ worker เข้าไปเรื่อย ๆ ให้ผลตอบแทนที่น้อยลงทุกครั้ง เพราะเพดานถูกกำหนดโดยสัดส่วน serial ไม่ใช่โดยจำนวน N
- ต้องรู้ก่อนว่าอะไรคือ serial — ก่อนจะลงทุนขนานงาน ต้องวัดให้ได้ก่อนว่าสัดส่วน P จริง ๆ คือเท่าไหร่ เพราะการเดาผิดทำให้ทุ่มทรัพยากรผิดจุด
- สมมติฐานคือ workload คงที่ — Amdahl’s Law มองปัญหาขนาดคงที่ (fixed problem size) ซึ่งต่างจาก Gustafson’s Law ที่มองว่าเมื่อมี processor มากขึ้น คนมักจะขยายขนาดงานตามไปด้วย (เช่น ประมวลผล dataset ที่ใหญ่ขึ้นในเวลาเท่าเดิม) ทำให้สัดส่วน serial ต่อ workload ทั้งหมดลดลงเมื่อ scale ขึ้น สองกฎนี้ไม่ได้ขัดแย้งกัน แต่ตอบคำถามคนละแบบ — Amdahl’s Law เหมาะกับคำถาม “งานเดิมจะเร็วขึ้นแค่ไหน” ส่วน Gustafson’s Law เหมาะกับคำถาม “งานที่ใหญ่ขึ้นจะเสร็จในเวลาเท่าเดิมได้แค่ไหน”
ในทางปฏิบัติ ระบบจริงมักมีเพดานที่ต่ำกว่าที่สูตรทำนายไว้ด้วยซ้ำ เพราะยังมีคอขวดอื่นที่ Amdahl’s Law ไม่ได้รวมไว้ เช่น memory bandwidth, I/O bandwidth, network latency และ overhead ของการประสานงานระหว่าง thread/process (contention, locking, context switching)
อีกคำที่มักใช้คู่กันในวงการ HPC (high-performance computing) คือ strong scaling ซึ่งหมายถึงการวัด speedup เมื่อเพิ่ม processor โดยขนาดปัญหาคงที่ — นี่คือสิ่งที่ Amdahl’s Law อธิบายโดยตรง ตรงข้ามกับ weak scaling ที่ขนาดปัญหาขยายตามจำนวน processor ซึ่งใกล้เคียงกับมุมมองของ Gustafson’s Law มากกว่า การแยกสองคำนี้ให้ออกช่วยให้เลือกใช้สูตรและตั้งความคาดหวังได้ถูกต้องตั้งแต่ต้น
flowchart LR Start --> Serial Serial --> Split Split --> W1 Split --> W2 Split --> W3 W1 --> Join W2 --> Join W3 --> Join Join --> Serial2 Serial2 --> End
แผนภาพข้างต้นแสดงงานหนึ่งชิ้นที่มีช่วง serial สองช่วง (Serial, Serial2) คั่นหัวท้ายของช่วงที่ทำขนานได้ (Split กระจายงานให้ W1, W2, W3 แล้ว Join รวมผล) ไม่ว่าจะเพิ่ม worker ในช่วงกลางกี่ตัว เวลารวมของ Serial และ Serial2 ก็ยังคงเท่าเดิมเสมอ — นี่คือภาพจำลองของ (1 − P) ในสูตร
ตัวอย่างในโลกจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างในโลกจริง”การประชุมทีมที่รอคนปั่นจักรยาน — ตัวอย่างคลาสสิกที่ใช้อธิบายกฎนี้คือ เพื่อนร่วมงานสามคนต้องไปถึงห้องประชุมพร้อมกัน สองคนขับรถถึงเร็ว อีกคนปั่นจักรยานมาช้ากว่า ถ้าทีมอยากเริ่มประชุมเร็วขึ้น การซื้อรถที่เร็วขึ้นให้สองคนแรกไม่ช่วยอะไรเลย เพราะตัวที่กำหนดเวลาเริ่มประชุมคือความเร็วของคนปั่นจักรยาน — ต้องแก้ที่ bottleneck ไม่ใช่ที่ส่วนที่เร็วอยู่แล้ว
เว็บไซต์ e-commerce ที่เพิ่ม server แต่ยังช้า — ร้านค้าออนไลน์เพิ่มจำนวน application server เพื่อรองรับ traffic ที่พุ่งขึ้นหลังเปิดตัวสินค้าใหม่ แต่พบว่าความเร็วโดยรวมยังถูกจำกัดด้วยเวลาที่แต่ละหน้าเว็บถูก serve ออกมา (เช่น query ฐานข้อมูลที่ทำงาน serial หรือ third-party API ที่ต้องรอ) การเพิ่ม server แนวนอนช่วยได้แค่ในส่วนที่ขนานได้เท่านั้น ส่วนที่เป็นคอขวดจริง ๆ ต้องได้รับการ optimize โดยตรง
Microservices ที่ยังชนกันที่ shared service — ทีม1 decompose monolith เป็น microservices จำนวนมากเพื่อ scale แนวนอน แต่ทุก request ยังต้องผ่าน authentication service ตัวเดียวที่เป็น serial bottleneck ไม่ว่าจะเพิ่ม instance ของ microservices อื่นกี่ตัว throughput รวมของระบบก็ไม่มีวันเกินความสามารถของ auth service ตัวนั้น — สะท้อนให้เห็นว่าการ scale สถาปัตยกรรมต้องมองหา serial path ที่แท้จริง ไม่ใช่แค่จำนวน service
CPU multicore กับซอฟต์แวร์ที่ไม่รองรับ multithreading เต็มที่ — การเพิ่ม core ให้ CPU จาก 4 เป็น 64 core ไม่ได้ทำให้โปรแกรมที่มีส่วน initialization, I/O หรือ algorithm บางส่วนที่เขียนแบบ single-threaded เร็วขึ้นตามสัดส่วน core ที่เพิ่ม เพราะส่วนเหล่านั้นยังคงรันแบบ serial เสมอ นี่คือเหตุผลที่ทีม hardware และ compiler ทุ่มเทมหาศาลกับการลดสัดส่วน serial code ในระบบปฏิบัติการและ runtime
Build pipeline ใน CI/CD ที่ยังรอ step เดียว — ทีมหนึ่งแตก unit test ออกเป็นหลาย job รันขนานกันบน CI runner จำนวนมากเพื่อให้ pipeline เร็วขึ้น แต่ step สุดท้ายอย่างการ build Docker image หรือ deploy ยังคงต้องรอให้ทุก job ก่อนหน้าเสร็จก่อน และตัว build/deploy เองก็ยังเป็น serial step เดียว ผลคือต่อให้เพิ่ม runner ขนาน test มากแค่ไหน เวลารวมของ pipeline ก็ยังถูกจำกัดด้วยเวลาของ step ที่ขนานไม่ได้เหล่านั้น
บทเรียนที่นำไปใช้
หัวข้อที่มีชื่อว่า “บทเรียนที่นำไปใช้”- วัดก่อนขนาน — ใช้ profiler หาให้เจอว่าสัดส่วนเวลาจริง ๆ ที่ใช้ไปกับส่วนที่ทำขนานได้คือเท่าไหร่ ก่อนตัดสินใจลงทุนเวลาเขียน concurrent code หรือซื้อ hardware เพิ่ม
- โฟกัสที่คอขวดจริง ไม่ใช่ส่วนที่ optimize ง่าย — การเร่งความเร็วส่วนที่ใช้เวลาน้อยอยู่แล้วให้ผลตอบแทนต่ำเสมอ ต่อให้ optimize ได้เก่งแค่ไหนก็ตาม ควรเทียบสัดส่วน P ก่อนเสมอว่าคุ้มค่ากับความซับซ้อนที่เพิ่มขึ้นหรือไม่
- แยกให้ออกระหว่าง “งานคงที่” กับ “งานที่ขยายได้” — ถ้าปัญหาที่แก้อยู่คือทำงานเดิมให้เร็วขึ้น ให้คิดแบบ Amdahl’s Law แต่ถ้าปัญหาคือรองรับ workload ที่ใหญ่ขึ้นเรื่อย ๆ ในเวลาเท่าเดิม ให้คิดแบบ Gustafson’s Law ทั้งสองมุมมองนำไปสู่กลยุทธ์ scale ที่ต่างกัน
- ใช้ได้กับทีมและกระบวนการ ไม่ใช่แค่ CPU — ถ้าการตัดสินใจทางสถาปัตยกรรมทั้งหมดต้องผ่านคนคนเดียวหรือคณะกรรมการกลุ่มเดียว การจ้างวิศวกรเพิ่มก็ไม่ช่วยเร่ง throughput ของการตัดสินใจ เพราะ “คนอนุมัติ” กลายเป็นส่วน serial ของกระบวนการทั้งหมด
- เพดานทางทฤษฎีต่ำกว่าที่คิดเสมอในระบบจริง — เผื่อใจไว้ว่าปัจจัยอื่นอย่าง lock contention, network latency, memory bandwidth จะทำให้ speedup จริงต่ำกว่าตัวเลขที่สูตรคำนวณได้
// คำนวณ speedup ตาม Amdahl's Law// parallelFraction คือสัดส่วนงานที่ทำขนานได้ (0.0 - 1.0)// processorCount คือจำนวน processor หรือ worker ที่ใช้static double AmdahlSpeedup(double parallelFraction, int processorCount){ double serialFraction = 1.0 - parallelFraction; return 1.0 / (serialFraction + (parallelFraction / processorCount));}
// ตัวอย่าง: งานที่ขนานได้ 90% เมื่อเพิ่ม processor ไปเรื่อย ๆ// AmdahlSpeedup(0.9, 2) ≈ 1.82// AmdahlSpeedup(0.9, 8) ≈ 4.71// AmdahlSpeedup(0.9, 1000) ≈ 9.91 -- เข้าใกล้เพดาน 1 / (1 - 0.9) = 10 แต่ไม่มีวันถึง