36 คะแนน โดย GN⁺ 2026-03-19 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • กฎข้อ 1: ไม่มีทางคาดเดาได้ว่าโปรแกรมจะใช้เวลาไปกับส่วนไหน คอขวดมักเกิดขึ้นในจุดที่คาดไม่ถึง ดังนั้น อย่าพยายามปรับปรุงความเร็วจนกว่าจะพิสูจน์ได้จริงว่าเป็นคอขวด
  • กฎข้อ 2: ต้องวัดผลก่อน การปรับแต่งความเร็วควรทำหลังจากวัดผลเท่านั้น และควรพิจารณาการทำ optimization เฉพาะเมื่อโค้ดบางส่วนครอบงำเวลาทำงานทั้งหมด
  • กฎข้อ 3: อัลกอริทึมที่ซับซ้อนจะช้าเมื่อ n ยังเล็ก อัลกอริทึมที่ซับซ้อนมีค่าคงที่สูง ดังนั้นถ้า n ไม่ได้โตบ่อย ๆ ก็ควรใช้วิธีที่เรียบง่ายกว่า แม้ n จะโตขึ้น ก็ควรใช้กฎข้อ 2 ก่อน
  • กฎข้อ 4: อัลกอริทึมที่ซับซ้อนมีบั๊กมากและนำไปใช้งานได้ยาก การใช้อัลกอริทึมที่เรียบง่ายและโครงสร้างข้อมูลที่เรียบง่ายจึงเป็นทางเลือกที่พึงประสงค์
  • กฎข้อ 5: ข้อมูลคือหัวใจสำคัญ หากเลือกโครงสร้างข้อมูลที่ถูกต้องและจัดระเบียบได้ดี อัลกอริทึมก็แทบจะปรากฏออกมาเองอย่างชัดเจน แกนกลางของการเขียนโปรแกรมไม่ใช่อัลกอริทึม แต่คือโครงสร้างข้อมูล

ปรัชญาและคำกล่าวที่เกี่ยวข้อง

  • กฎข้อ 1 และ 2 มีความหมายเดียวกับคำกล่าวของ Tony Hoare ที่ว่า “การทำ optimization ตั้งแต่เนิ่น ๆ คือรากเหง้าของความชั่วร้ายทั้งปวง”
  • Ken Thompson ตีความกฎข้อ 3 และ 4 ใหม่เป็น “ถ้ายังไม่แน่ใจ ให้ใช้วิธีง่าย ๆ (Brute Force)”
  • กฎข้อ 3 และ 4 เป็นตัวอย่างของปรัชญาการออกแบบแบบ KISS(Keep It Simple, Stupid)
  • กฎข้อ 5 เป็นสิ่งที่ Fred Brooks ได้กล่าวไว้แล้วใน 『The Mythical Man-Month』 โดย
    มักถูกสรุปว่า “เขียนโค้ดที่เรียบง่ายโดยใช้อ็อบเจ็กต์ที่ชาญฉลาด”

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

 
GN⁺ 2026-03-19
ความคิดเห็นจาก Hacker News
  • ทำให้นึกถึงบรรยายของ Jonathan Blow
    เขาเข้าหาจากมุมมองของประสิทธิภาพในการทำงาน ช่วงแรกของการพัฒนา Braid เขาทำแทบทุกอย่างด้วยอาร์เรย์แบบเรียบง่าย และค่อยแก้เมื่อเกิดคอขวดเท่านั้น
    ประโยคที่น่าประทับใจคือ “สิ่งที่สำคัญกว่าความเร็วหรือหน่วยความจำ คือเวลาชีวิตของคนที่ต้องใช้เพื่อสร้างโปรแกรมหนึ่งขึ้นมา”

    • เกมต้องประมวลผลอ็อบเจ็กต์คล้าย ๆ กันจำนวนมากซ้ำ ๆ ที่มากกว่า 60 เฟรมต่อวินาที ดังนั้นโครงสร้างแบบอิงอาร์เรย์ที่เรียบง่ายจึงเป็นค่าเริ่มต้นที่สมเหตุสมผล
    • ในการพัฒนาเกม ต้องเรนเดอร์เฟรมให้เสร็จภายใน 16ms จึงมักใช้ตารางขนาดคงที่และการค้นหาเชิงเส้น เป็นทางเลือกเชิงโครงสร้างเพื่อหลีกเลี่ยงความคาดเดาไม่ได้ของการจัดสรรหน่วยความจำแบบไดนามิก
    • มุมมองนี้ใช้ได้กับงานพัฒนาทั่วไปนอกเหนือจากเกมด้วย เพราะเราทุกคนทำงานภายใต้ข้อจำกัดด้านเดดไลน์และต้นทุน ดังนั้นเวลาเชิงวิศวกรรมเองก็เป็นต้นทุน
    • แต่ก็ควรระวังในการขยายบทเรียนจากเกมไปใช้กับการเขียนโปรแกรมทั่วไปตรง ๆ เกมเน้นโค้ดเฉพาะทางมากกว่าการนำกลับมาใช้ซ้ำ ขณะที่ซอฟต์แวร์ทั่วไปเน้นข้อมูลเป็นศูนย์กลาง
    • สำหรับผม ผมเป็นพวกที่เริ่มจากแฮชแมปแทนอาร์เรย์
  • ถ้ายอมรับ Rule 1 อย่างจริงจัง Rule 3~5 ก็จะตามมาเองอย่างเป็นธรรมชาติ
    เมื่อยอมรับสมมติฐานว่าคาดเดาคอขวดไม่ได้ การเขียนโค้ดแบบเรียบง่ายและวัดผลจึงเป็นกลยุทธ์เดียวที่สมเหตุสมผล
    ในทางปฏิบัติ สิ่งที่ล้มเหลวบ่อยที่สุดไม่ใช่การทำ optimization เร็วเกินไป แต่คือการทำ abstraction เร็วเกินไป สร้างชั้นความซับซ้อนเพื่อความยืดหยุ่นที่ยังไม่จำเป็น และสุดท้ายกลับเพิ่มภาระการดูแลรักษา

    • ในทีมมักอ้างคำพูดที่ว่า “abstraction ควรค่อย ๆ เกิดขึ้นเองตามธรรมชาติ ไม่ใช่ถูกออกแบบไว้ล่วงหน้า”
    • abstraction ที่มาเร็วเกินไปทำให้เสียเวลานักพัฒนา เพิ่มtechnical debt และเพิ่มโอกาสเกิดบั๊ก หลายครั้งเกิดขึ้นภายใต้ข้ออ้างว่า ‘เตรียมไว้สำหรับปัญหาในอนาคต’
    • ถ้าจะอ่านงานที่เกี่ยวข้อง บทความของ Philip Wadler ก็น่าสนใจ
    • ผมให้ความสำคัญกับความอ่านง่ายและการบำรุงรักษามากกว่าประสิทธิภาพ สำหรับผม Rule 4 จึงเป็นรากฐาน และ Rule 1 เป็นผลลัพธ์ของมัน
    • เคยเจอกรณีที่แยกไฟล์คอนฟิกซับซ้อนเกินไป สุดท้ายแค่ไฟล์ YAML ธรรมดา 8 ไฟล์ก็พอแล้ว
  • ผมเคยมีประสบการณ์ต้องทำฟีเจอร์ค้นหาชุดข้อมูลตอนตี 2 ในยุค 90
    ตอนนั้นเหนื่อยมากเลยทำแบบการค้นหาเชิงเส้นไปก่อนแล้วค่อยคิดว่าจะกลับมาแก้ทีหลัง แต่ในความเป็นจริงมันต่างกันแค่ 6 วินาทีจากการทดสอบที่กินเวลา 4 ชั่วโมง
    สุดท้ายก็แก้อยู่ดี แต่แทบไม่มีความต่างที่มีนัยสำคัญ

    • เมื่อ n เล็ก การค้นหาเชิงเส้นอาจเร็วกว่าเสียอีก
    • แต่ algorithm แบบ O(n²) ก็มีความเสี่ยงแบบ “ปล่อยขึ้น production ได้ แต่สุดท้ายก็ระเบิดตอนรันจริง
  • เห็นด้วยกับ Rule 5 แบบเต็มร้อย
    ยิ่งปัญหายากเท่าไร ก็ยิ่งแก้ได้ด้วยการปรับปรุงโครงสร้างข้อมูลและ API แบบวนซ้ำ เมื่อโครงสร้างลงตัวแล้ว control flow จะเป็นธรรมชาติเอง
    LLM ไม่ค่อยเก่งกับการคิดเชิงโครงสร้างแบบนี้ มันเสนอ control flow ที่ซับซ้อนได้ดี แต่ไม่ค่อยถนัดการออกแบบโครงสร้างข้อมูลที่นำมาประกอบต่อกันได้

    • จากประสบการณ์ของผม Rule 5 นี่แหละคือ Rule 1 ที่แท้จริง มีคำพูดว่า “ถ้าให้ดูโค้ดจะยิ่งงง แต่ถ้าให้ดูdatabase schema ทุกอย่างจะชัดเจน”
    • ใน distributed service มักมองข้าม Rule 5 บ่อย ๆ แทนที่จะแยกเป็นหลาย HTTP·DB call โครงสร้างที่จัดการได้ในครั้งเดียวมักมีประสิทธิภาพกว่า
  • ผมมาจากสายวิศวกรรมไฟฟ้า (E.E.) และช่วงต้นอาชีพก็แทบไม่มีปัญหาใหญ่เพราะRule 3
    แต่ภายหลังเมื่อย้ายไปทำระบบขนาดใหญ่ n ก็โตขึ้น และความซับซ้อนก็กลายเป็นสิ่งสำคัญจริง ๆ
    ‘big iron’ ในยุคที่ Rob Pike พูดถึงนั้น คล้ายกับสภาพแวดล้อมแบบ embeddedในปัจจุบัน

    • ผมเห็นต่างกับ Rule 3 อยู่บ้าง สำหรับ input เล็กอาจไม่สำคัญ แต่ถ้า input ใหญ่ประสิทธิภาพแบบ Big-Oสำคัญมาก
      ถ้าอัลกอริทึมสองแบบมีความยากในการ implement พอ ๆ กัน ผมจะเลือกตัวที่เร็วกว่าเมื่อ input ใหญ่
      บทความที่เกี่ยวข้อง: Less Than Quadratic
    • คำว่า “fancy” ต้องตีความให้เข้ากับโดเมนของปัญหา ถ้า n เล็ก วิธีง่าย ๆ มักดีกว่า แต่ถ้า n ใหญ่ อัลกอริทึมขั้นสูงก็จำเป็น
      หลายครั้งผู้คนเลือกวิธี O(n²) ที่ง่ายเกินไปแล้วต้องเจอกับการระเบิดตอนรันจริง
    • พ่อของผมชอบใช้lookup tableมาตั้งแต่ยุค Fortran เป็นวิธี optimization แบบคลาสสิกที่ช่วยลดการคำนวณซ้ำ
  • ผมคิดว่ากฎของ Pike ดีกว่าสุภาษิตเดิม ๆ
    ประโยคอย่าง “optimization ที่เร็วเกินไปคือรากเหง้าของความชั่วร้ายทั้งมวล” พอขาดบริบทแล้วก็มักถูกเข้าใจผิดได้ง่าย
    เวอร์ชันของ Pike ชัดเจนและเอาไปใช้ผิดได้ยากกว่า
    เคยมีคำสรุปเก่าว่า “Rule 5 คือ ‘ให้โค้ดโง่ ๆ ใช้อ็อบเจ็กต์ฉลาด ๆ’” แต่
    ในยุค object-oriented มันกลับบิดเบี้ยวกลายเป็นความเชื่อผิด ๆ ที่ซ่อนความซับซ้อน

    • การรักษาสายใยความต่อเนื่องของแนวคิดทางประวัติศาสตร์เป็นเรื่องสำคัญ
    • คำว่า “mental clickbait ของสุภาษิตคลาสสิก” ให้ความรู้สึกเป็นอุปมาแบบมีไหวพริบอย่างหนึ่ง
  • หลังจากดูแล codebase เดิมมานานกว่าสิบปี ผมซึมซับกฎของ Pike เข้าไปเต็มที่แล้ว
    การยึดหลักKISS/DRY และรักษาเทคโนโลยีที่พิสูจน์แล้วแทนการวิ่งตามของใหม่ เป็นสิ่งที่มั่นคงกว่าในระยะยาว
    จริง ๆ แล้วเอกสารหลักการพัฒนาของทีมเราก็แทบเหมือนกับกฎของ Pikeทุกประการ

  • ในยุค C++ ช่วงต้นทศวรรษ 1990 ผมเคยเปลี่ยนจากdoubly linked listมาใช้การขยายอาร์เรย์ธรรมดาแทน
    ไม่เพียงแต่บั๊กหายไป แต่ยังเร็วขึ้นอีกด้วย
    ผมได้เรียนรู้ว่าการทำ operation ที่แพงให้น้อยครั้งลงเป็นกลยุทธ์ที่ดี

  • เมื่อนำกฎ “อย่าปรับแต่งโดยไม่วัดผล” ไปเทียบกับ Latency Numbers Every Programmer Should Know ของ Jeff Dean ก็ชวนให้คิด
    Dean บอกว่าเราสามารถคาดเดาประสิทธิภาพได้จากความรู้ล่วงหน้า
    สุดท้ายสองมุมมองนี้ก็อยู่ร่วมกันได้ — ในขั้นออกแบบให้เลือกโครงสร้างที่ดูเร็วโดยสัญชาตญาณ และหลัง implement แล้วค่อยปรับละเอียดโดยอิงการวัดผล

    • โค้ดที่เร็วจริงจะใช้ทั้งสองแนวทาง ในขั้นออกแบบจะคำนึงถึงประสิทธิภาพของแคช และหลังจากนั้นใช้ profiling เพื่อตัดคอขวดออก
    • ตัวเลข latency บอกได้เพียงขีดจำกัดทางทฤษฎีของอัลกอริทึม แต่ประสิทธิภาพจริงขึ้นอยู่กับ implementation และสภาพแวดล้อมตอนรัน
    • สิ่งที่ ‘optimization เร็วเกินไป’ ห้ามจริง ๆ คือการจูนแบบแฮ็กเฉพาะจุด ไม่ใช่การคิดเรื่องความเร็วในระดับการออกแบบทั้งระบบ
  • Rule 1 และ 2 เป็นกฎแบบสัมบูรณ์เฉพาะตอนรับมือกับปัญหาใหม่เท่านั้น
    ถ้าสร้างระบบในโดเมนเดิมซ้ำ ๆ ก็จะสามารถคาดเดาจุดคอขวดล่วงหน้าได้
    นักพัฒนาที่มีประสบการณ์มักมีเซนส์ด้านประสิทธิภาพคร่าว ๆ ตั้งแต่ก่อนเริ่มออกแบบ

    • ผมเองก็คาดเดาคอขวดได้แม่นในแทบทุกกรณี และหลายครั้งก็เปลี่ยนแนวทางจากการทดสอบประสิทธิภาพล่วงหน้า
    • แต่จากประสบการณ์ 30 ปี แม้จะเดาถูกว่าคอขวดจะเกิด แต่ตำแหน่งหรือจังหวะที่แน่ชัดนั้นคาดเดาไม่ได้
    • มีมุกว่า “Rob Pike จะไปรู้อะไร” แต่เขาคือคนที่สร้างUnix และ Go กฎของเขาย่อมมีเหตุผล