2 คะแนน โดย GN⁺ 2024-11-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Ninja ที่เปิดตัวในปี 2011 เริ่มต้นจากระบบบิลด์ที่คล้ายกับ Make และกลายเป็นกรณีตัวอย่างความสำเร็จของโอเพนซอร์สที่แพร่หลายไปถึง Chrome, บางส่วนของ Android, Meson และโปรเจกต์ที่ใช้ CMake
  • แกนที่ตัดสินความสำเร็จหรือความล้มเหลวไม่ใช่ตัวโค้ดเองเท่านั้น แต่คือ สถาปัตยกรรม และยิ่งไปกว่านั้นคือ ปัญหาทางสังคม เช่นการบำรุงรักษาและความคาดหวังของผู้ใช้
  • Ninja อ่านคำสั่ง อินพุต และเอาต์พุตที่ระบุไว้ใน ninja.build ตรวจสอบเวลาแก้ไขไฟล์ แล้วรันงานที่จำเป็นแบบขนาน โดยมุ่งทำให้ incremental build ของโปรเจกต์ขนาดใหญ่เร็วขึ้น
  • การตัดสินใจด้านการออกแบบที่ใหญ่ที่สุดคือไม่รวมฟีเจอร์บิลด์ระดับสูงไว้ในตัวเอง แต่คงบทบาทไว้ที่ตัวรัน action graph และแยกให้ตัวสร้าง (generator) รับผิดชอบการตัดสินใจที่ซับซ้อน
  • การรวมเข้ากับ CMake, การรองรับ Windows และค่าเริ่มต้นของการรันแบบขนานช่วยให้แพร่หลายขึ้น แต่คำขอและ contribution ที่ขัดกับเป้าหมายการออกแบบก็ยังเป็นภาระใหญ่ของการดูแลโอเพนซอร์ส

บริบทที่ทำให้ Ninja ถูกใช้อย่างแพร่หลาย

  • Ninja เป็นระบบบิลด์ที่คล้าย Make ซึ่งเปิดตัวเมื่อราว 9 ปีก่อน ตอนแรกเป็นเพียง side project ที่แชร์ออกไปแบบค่อนข้างเขิน ๆ แต่ต่อมากลับถูกใช้อย่างกว้างขวาง
  • กรณีการใช้งานหลักมีดังนี้
    • Chrome ลบ การบิลด์ที่ไม่ใช่ Ninja ออกไปในที่สุด
    • Android ใช้ Ninja กับบางองค์ประกอบขนาดใหญ่ของระบบ
    • โปรเจกต์ Meson ใช้ Ninja และดูเหมือนจะเป็นระบบบิลด์ที่ถูกใช้มากขึ้นเรื่อย ๆ ในโลกซอฟต์แวร์เสรี
    • มีหลายโปรเจกต์ที่ใช้ Ninja ร่วมกับ CMake และคู่มือการบิลด์ของ Swift ก็ระบุว่าต้องติดตั้ง Ninja
  • Ninja ออกรุ่นในปี 2011, มีการส่งต่อความเป็นเจ้าของโปรเจกต์ในปี 2014 และต่อมาถูกส่งต่อไปยังเมนเทนเนอร์หลักคนที่สาม
  • ประสบการณ์สำคัญที่เห็นจากการย้อนมองครั้งนี้คือ สิ่งที่มีผลมากกว่าโค้ดคือ สถาปัตยกรรม และสิ่งที่มีผลมากกว่าสถาปัตยกรรมคือ ประเด็นทางสังคม

การทำงานพื้นฐานเพื่อให้บิลด์เร็ว

  • สิ่งที่ Ninja ทำค่อนข้างเรียบง่าย
    • ผู้ใช้ระบุคำสั่งที่จะรัน ไฟล์ที่แต่ละคำสั่งใช้ และไฟล์ที่สร้าง ในไฟล์ ninja.build
    • Ninja อ่านไฟล์นี้และตรวจสอบ เวลาแก้ไข ของไฟล์ต่าง ๆ
    • จากนั้นรันคำสั่งที่จำเป็นเพื่อให้ได้สถานะล่าสุดแบบขนาน
  • เมื่อเทียบกับ Make ความสามารถของภาษาที่ใช้เขียนอินพุตมีน้อยกว่า และเน้นโครงสร้างที่ทำงานฟีเจอร์จำนวนน้อยเหล่านั้นได้เร็วมาก
  • ลำดับการทำงานแบ่งเป็น 3 ขั้น
    • parse และตีความไฟล์บิลด์
    • ตรวจสอบเวลาแก้ไขของไฟล์อินพุต
    • รันคำสั่งที่จำเป็น
  • เป้าหมายคือไปถึงขั้นที่สามให้เร็วที่สุดเท่าที่ทำได้ แม้ในโปรเจกต์ขนาดใหญ่ที่มีไฟล์อินพุตมากกว่า 100,000 ไฟล์
  • ตัวอย่างการเพิ่มประสิทธิภาพคือ Ninja จะ map พาธของไฟล์อินพุตให้เป็นอ็อบเจ็กต์หน่วยความจำที่ไม่ซ้ำกันให้เร็วที่สุดเท่าที่เป็นไปได้ และหลังจากนั้นใช้การเปรียบเทียบ pointer สำหรับตรวจสอบความเหมือนของพาธ
  • รายละเอียดด้านประสิทธิภาพในระดับที่ลึกกว่านี้กล่าวถึงไว้ใน บทของ Ninja ใน The Performance of Open Source Software
  • ตลอดหลายปีที่ผ่านมา มีการ reimplement Ninja หลายตัวด้วย
    • llbuild และ Shake รองรับไฟล์ Ninja เป็นอินพุต
    • samurai เป็นโปรเจกต์ที่แทบจะเขียนใหม่แบบไฟล์ต่อไฟล์ โค้ดน้อยกว่าแต่ฟีเจอร์ก็น้อยกว่าและไม่มีการทดสอบ
  • การทำส่วนที่สนุก 20% แรกนั้นง่าย แต่ 80% ที่เหลือเต็มไปด้วยรายละเอียด และยังไม่มี implementation ที่เร็วกว่าเป็นที่รู้จัก

ทางเลือกด้านการออกแบบที่สำคัญ

  • การแทนกราฟ

    • Make จัดการกรณีที่กฎบิลด์หนึ่งข้อสร้างหลายไฟล์ได้ไม่ดีนัก
    • Ninja ใช้ กราฟสองฝั่งระหว่างไฟล์กับคำสั่ง แทนที่จะเป็นกราฟระหว่างไฟล์อย่างเดียว
    • โหนดไฟล์จะเป็นเส้นเชื่อมเข้าไปยังโหนดคำสั่ง และโหนดคำสั่งจะมีเส้นเชื่อมออกไปยังไฟล์เอาต์พุต
    • โครงสร้างนี้จับรูปแบบที่แท้จริงของบิลด์ได้ดีกว่า
    • ถ้าอินพุตตัวใดตัวหนึ่งเปลี่ยน คำสั่งนั้นจะล้าสมัย และเมื่อรันคำสั่งแล้ว เอาต์พุตทั้งหมดจะถูกอัปเดต
    • ไฟล์หนึ่งไฟล์สามารถมีเส้นเชื่อมขาเข้าได้อย่างมากหนึ่งเส้น
    • ตัว command line เองก็อาจมองได้ว่าเป็นอินพุตของโหนดคำสั่ง ดังนั้นเมื่อแฟล็กบน command line เปลี่ยน คำสั่งและเอาต์พุตก็จะกลายเป็นสถานะล้าสมัย
  • deps log และ dependency ของ C header

    • หากต้องการจัดการ dependency ของ C header ให้ถูกต้อง จำเป็นต้องใช้ข้อมูล dependency เพิ่มเติมที่ C compiler สร้างขึ้น
    • ระหว่างการเลือกว่าใช้ฐานข้อมูลหรือรักษาความเรียบง่าย ผู้เขียนได้สร้าง ฟอร์แมตการแทนค่า deps log ขึ้น
    • ฟอร์แมตนี้ค่อนข้างบีบอัดได้ดี แต่ก็ยังผิดอยู่ในบางแง่มุมที่สำคัญ
  • การออกแบบแบบ end-to-end / crash-only

    • Ninja ไม่ใช่ daemon process ที่รันค้างไว้ตลอด แต่จะเริ่มทำงานใหม่จากต้นทุกครั้งที่รัน
    • ทางเลือกนี้เป็นโครงสร้างที่ได้รับอิทธิพลจาก end-to-end principle และ crash-only software
    • ถ้าทำให้เส้นทางการเริ่มใหม่จากต้นเร็วพอ ก็ไม่จำเป็นต้องสร้างเส้นทางโค้ด “ออนไลน์” แยกต่างหาก
    • โปรเจกต์ที่สามารถอยู่ค้างในหน่วยความจำได้มักลงเอยด้วยการปล่อยให้ประสิทธิภาพตอนเริ่มต้นถูกละเลย
  • การตรวจสอบสถานะไฟล์

    • เหตุผลหนึ่งที่คนคาดหวังว่าเครื่องมือบิลด์ควรอยู่ค้างในหน่วยความจำ คือเพื่อ cache สถานะของไฟล์บนดิสก์
    • ในความเป็นจริง kernel cache ข้อมูลนี้ไว้ในหน่วยความจำอยู่แล้ว และการ cache ซ้ำใน userspace ก็ไม่ได้ช่วยประหยัดมากนัก
    • บน Linux การดึงสถานะไฟล์นั้นเร็วมาก และ Ninja ทำงานนี้บน single thread
    • แม้บนเครื่องที่ถือว่า “เร็ว” ตามมาตรฐานเมื่อ 10 ปีก่อน ก็ยังทำ stat กับไฟล์ 30,000 ไฟล์ได้ภายในเวลาเพียงไม่กี่สิบมิลลิวินาที
  • จุดประนีประนอมระหว่างสเกลกับสเปก

    • กฎแบบคร่าว ๆ คือการขยายขนาด 2 เท่ามักแก้ได้ด้วย optimization แต่ถ้าขยาย 10 เท่าจะต้อง re-architect
    • Ninja ถูกออกแบบโดยมี Chrome build ที่มีขั้นตอนบิลด์ราว 30,000 ขั้นเป็นศูนย์กลาง
    • ตอนนี้มันถูกใช้ในสภาพแวดล้อมที่เล็กกว่านั้นด้วย แต่ในกรณีเช่นนั้นข้อได้เปรียบด้านความเร็วอาจไม่จำเป็น
    • ในสภาพแวดล้อมที่ใหญ่กว่า เช่น Android build กำลังเริ่มชนข้อจำกัดด้านสเกล และอาจต้องการแนวทางอื่น
    • เพื่อให้รันแบบขนานได้ Ninja ต้องการให้ผู้ใช้ให้ข้อมูลมาเพียงพอ แต่ไม่ได้บังคับว่าต้องรู้ทุกอย่างเกี่ยวกับทั้งบิลด์
    • มีจุดประนีประนอมระหว่างความถูกต้อง ความสะดวก และประสิทธิภาพ และในระบบนิเวศโดยรวม เครื่องมือที่ยอมลดความถูกต้องลงบางส่วนเพื่อความสะดวก อาจให้ผลลัพธ์ที่ถูกต้องกว่าเครื่องมือที่ถูกต้องกว่าแต่ใช้งานลำบาก

อุปมาแบบ “assembler” ที่สร้างการแยกชั้น

  • ระบบบิลด์มีฟีเจอร์ระดับสูงหลากหลาย และแต่ละเครื่องมือก็อธิบายตัวเองกว้างขวางจนแทบเทียบกันตรง ๆ ได้ยาก
  • แกนความคิดสำคัญของ Ninja คือ ไม่ว่าระบบบิลด์จะมีฟีเจอร์ระดับสูงอะไร สุดท้ายแล้วมันต้องสร้าง action graph เพื่อทำให้ไฟล์อยู่ในสถานะล่าสุด
  • Ninja ทำหน้าที่รัน action graph นี้เท่านั้น และปล่อยให้ผู้ใช้เลือกเองว่าจะวาง generator แบบใดไว้ด้านบน
  • การแยกระหว่างสองโปรแกรมนี้ เดิมทีออกแบบมาให้เหมาะกับโปรเจกต์ Chrome แต่ต่อมากลายเป็น contribution ที่สำคัญที่สุดของ Ninja
  • ข้อดีมีสองอย่าง
    • ตัว Ninja เองยังคงเรียบง่ายและเร็ว
    • งานที่มีต้นทุนสูงอย่างการ glob *.c ถูกผลักไปไว้ฝั่ง generator
  • ต่างจากระบบบิลด์อื่นที่จัดการทุกอย่างในคราวเดียว โครงสร้างของ Ninja ทำให้ action graph ที่คำนวณแล้วถูกบันทึกเป็น snapshot ลงดิสก์
  • ผลลัพธ์คือมันกลายเป็นการ cache action graph ระหว่างรอบบิลด์
  • generator สามารถเป็นระดับสูงได้เท่าที่ต้องการ
    • ตัวอย่างเช่น อาจ glob ทั้ง source tree เพื่อหาไฟล์ที่มีคำว่า test อยู่ในชื่อแล้วถือว่าเป็น test ก็ได้
  • นักพัฒนาต้องตัดสินใจเองว่าต้องการจ่ายต้นทุนให้กับอะไร
    • ถ้า generator glob ทั้งดิสก์ก็ทำได้ แต่ก็จะเห็นชัดขึ้นด้วยว่าทำไมบิลด์นั้นถึงช้า
  • การแยกระหว่าง generator กับ action graph ไม่ได้เรียบง่ายนักในทางปฏิบัติ และใน Ninja เองก็มีรายละเอียดมากมายว่าชั้นไหนควรทำอะไร
  • ในทางทฤษฎี ระบบบิลด์ของ Xcode หรือ Visual Studio ก็สามารถคำนวณล่วงหน้าแล้ว snapshot ผลลัพธ์แบบเดียวกันได้ แต่ผู้เขียนมองว่ามักทำไม่สำเร็จเพราะแรงดึงดูดให้เอาชั้นต่าง ๆ มาปนกัน
  • Make พยายามรวมฟีเจอร์สำหรับโปรแกรมเมอร์ไว้ทั้งหมด ทั้ง globbing, การขยายตัวแปร, substring และฟังก์ชันต่าง ๆ จึงกลายเป็นภาษาที่อ่อนเกินกว่าจะอธิบายทุกสิ่งที่ต้องการได้ แต่ก็แรงพอจะทำให้คนเขียน Makefile ที่ช้าได้
  • Ninja พยายามหลีกเลี่ยงทิศทางนั้น

ค่าเริ่มต้นเป็นตัวกำหนดประสิทธิภาพที่ผู้ใช้รู้สึก

  • โดยปกติ Ninja จะ รันคำสั่งแบบขนาน
  • Make ก็รันแบบขนานได้ผ่านแฟล็ก -j แต่ค่าเริ่มต้นคือรันแบบลำดับเดียว
  • Makefile มักเขียนโดยระบุ dependency ไม่ครบ ทำให้ไม่ปลอดภัยเมื่อรันแบบขนาน
  • Ninja รันแบบขนานเสมอแม้บนระบบ single-core จึงทำให้ข้อผิดพลาดแบบนี้ถูกเปิดเผยได้เร็วกว่า
  • โปรแกรมที่เหมาะกับ Ninja มักจะปลอดภัยต่อการบิลด์แบบขนานด้วย
  • Ninja ไม่ได้มีระบบขั้นสูงสำหรับตรวจจับ dependency ที่ผิดพลาด แต่เป็นโครงสร้างที่ทำให้บิลด์ผิดเกิดบ่อยขึ้นจนปัญหาถูกมองเห็น
  • เนื่องจากผู้ใช้มักลืมหรือไม่รู้จักแฟล็กรันแบบขนานของ Make เพียงแค่ค่าเริ่มต้นนี้อย่างเดียวก็อาจทำให้ Ninja “รู้สึกว่าเร็วกว่าเกินสองเท่า” ในการใช้งานจริงเมื่อเทียบกับ Make
  • optimization ที่ผู้ใช้ไม่เคยได้สัมผัสจริง แทบไม่มีความหมาย

ตัวชี้วัดที่สำคัญต่อความเร็ว

  • ประสิทธิภาพของระบบบิลด์มีความหมายได้หลายแบบ
    • บิลด์เต็มตั้งแต่ต้นใช้เวลานานแค่ไหน
    • หลังจากบิลด์ไปแล้ว เมื่อแก้ไฟล์หนึ่งไฟล์และบิลด์ใหม่ จะใช้เวลานานแค่ไหน
  • Ninja มุ่งเน้นที่การเร่ง edit-compile loop ใน incremental build ของ codebase ขนาดใหญ่
  • ตอนเริ่มสร้าง Ninja ผู้เขียนพยายามไล่ตามความเร็วของ blaze หรือ bazel เพราะจำได้ว่ามันเร็วมาก
  • แต่เมื่อมองย้อนกลับไป blaze ไม่ได้เร็วเป็นพิเศษในตัวชี้วัดที่ตัวเองสนใจ และเพราะเป็นโปรแกรม Java แม้แต่การแสดงผล help ก็ยังช้า
  • เหตุผลที่ยึดติดกับ incremental build เพราะเชื่อว่าเวลารอในแต่ละรอบมีผลมากต่อความพึงพอใจของโปรแกรมเมอร์
  • Ninja ถูกใช้ใน edit-compile loop ซึ่งความต่างระหว่าง 1 วินาทีกับ 4 วินาทีนั้นสำคัญ
  • ความหมายของคำว่า “เร็ว” สื่อสารกับผู้ใช้ได้ยาก และคู่มือ Ninja ก็เตือนว่าในโปรเจกต์เล็ก ๆ อาจแทบไม่รู้สึกถึงผลด้านความเร็ว
  • แต่คำว่า “เร็ว” เป็นคำที่ขายได้ดี จึงทำให้ผู้ใช้แอปขนาดเล็กที่หงุดหงิดกับการขาดฟีเจอร์ก็ยังพยายามใช้ Ninja
  • แม้ Ninja จะเน้น incremental rebuild แต่ผู้ใช้บางรายก็รายงานว่าประสิทธิภาพของ full build ดีขึ้นด้วย
    • เพราะ Ninja แทบไม่ทำอะไรเลย จึงใช้ CPU ระหว่างบิลด์น้อยและแย่ง CPU จากงานบิลด์จริงน้อยลง
  • เอาต์พุตก็สั้นมาก
    • บิลด์ที่สำเร็จมักพิมพ์เพียงบรรทัดเดียว
    • ระบบบิลด์อื่นอาจพิมพ์หลายขั้นตอนและข้อมูลเวลาเป็นจำนวนมากจนให้ความรู้สึกหนัก
  • โครงสร้างที่พูดน้อยนี้ทำให้ Ninja ดู “เหมือนไม่มีตัวตน” มากขึ้น

การรวมกับ CMake และการรองรับ Windows ที่ช่วยขยายการใช้งาน

  • CMake

    • เดิมที Ninja ถูกสร้างมาเพื่อใช้กับระบบบิลด์เฉพาะของ Chrome
    • Peter Collingbourne เป็นผู้ทำงานเชื่อม Ninja เข้ากับระบบบิลด์ CMake ที่ถูกใช้อย่างแพร่หลายกว่า
    • การรวมนี้เริ่มจากงานของ LLVM และต้องเพิ่ม semantics ใหม่ทั้งฝั่ง CMake และฝั่ง Ninja
    • Peter ถูกยกให้เป็นคนที่มีผลงานสำคัญที่สุดต่อความสำเร็จของ Ninja ในโลกจริง
    • หลังจากนั้นผู้เขียน CMake เข้ามารับช่วงการรวมนี้ต่อ แต่ยังไม่สามารถตอบสนองต่อคำขอและข้อกังวลได้เพียงพอ
    • ผู้เขียนไม่เคยใช้ CMake โดยตรงเลยจนถึงตอนนี้
  • Windows

    • เพราะ Chrome รองรับ Windows ด้วย Ninja จึงต้องทำงานบน Windows ได้เช่นกัน
    • ส่วนใหญ่ของการรองรับ Windows ถูกเขียนโดยผู้ร่วมพัฒนา
    • ในเชิงเทคนิค การรองรับ Windows เป็นความยุ่งยากใหญ่พอสมควร
    • การรัน process และการจับเอาต์พุตมีความต่างระดับที่ต้องไปเรียนรู้ API ของแต่ละแพลตฟอร์มใหม่
    • การออกแบบของ Ninja อาศัยสมมติฐานว่าสามารถดึงเวลาแก้ไขล่าสุดของไฟล์ที่ kernel cache ไว้ได้อย่างรวดเร็ว แต่บน Windows สมมติฐานนี้ไม่เป็นจริง
    • ถึงอย่างนั้น Windows ก็เป็นแพลตฟอร์มที่มีฐานนักพัฒนาขนาดใหญ่
    • เครื่องมือดี ๆ สำหรับ Linux มักเกิดจากแรงผลักให้แบ่งปัน แต่เครื่องมือสำหรับ Windows มักเกิดจากแรงผลักให้ขาย ทำให้มีเครื่องมือที่เปิดให้อิสระค่อนข้างน้อยกว่า
    • การที่ผู้ใช้ช่วงแรกของ Ninja จำนวนมากเป็นผู้ใช้ Windows เคยเป็นเรื่องน่าประหลาดใจ แต่เมื่อคิดถึงจำนวนนักพัฒนาบน Windows ที่มากอยู่แล้ว แค่มีบางส่วนสนใจก็กลายเป็นผู้ใช้จำนวนมากได้

การทำความเข้าใจงานที่เกี่ยวข้อง

  • Ninja เริ่มจากการแฮ็กเดโมเล่นช่วงสุดสัปดาห์ จึงน่าเสียดายที่ก่อนสร้างไม่ได้สำรวจงานที่เกี่ยวข้องให้เพียงพอ
  • เวลาสร้างอะไรขึ้นมา การเข้าใจ design space อย่างแท้จริงเป็นเรื่องสำคัญ
  • คำว่า “action graph” ไม่ใช่คำที่ใช้ตอนสร้าง Ninja แต่ยืมมาจากระบบบิลด์ blaze/bazel ของ Google
  • bazel อธิบายว่ากราฟของ target ระดับสูงอย่าง “library” หรือ “binary” จะสร้าง action graph ซึ่งเป็นกราฟของคำสั่ง
  • แนวคิดที่มองข้อความ command line เป็นอินพุตเช่นเดียวกับไฟล์ เป็นตัวอย่างหนึ่งของ incremental computation
  • incremental computation ไม่ได้เชื่อมโยงกับระบบบิลด์เท่านั้น แต่ยังเกี่ยวข้องกับความเป็น incremental ของ UI ด้วย
  • บนบล็อกของ Jane Street มี บทความแนะนำ Incremental ซึ่งเชื่อมโยงกับแนวทางสร้าง UI แบบ React ได้เช่นกัน
  • "Build Systems à la Carte" เป็นงานวิจัยที่พูดถึง incremental computation ในบริบทของระบบบิลด์ และถูกยกให้เป็นเอกสารที่น่าเสียดายที่ไม่มีตั้งแต่ก่อนเขียน Ninja

ภาระของการดูแลโอเพนซอร์ส

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

เมนเทนเนอร์และผู้ร่วมพัฒนา

  • Nico Weber เป็นผู้ร่วมงานที่รอบคอบ และทำหน้าที่เป็นเมนเทนเนอร์ของ Ninja อยู่หลายปี
  • Jan Niklas Hasse รับช่วงโปรเจกต์ต่อจาก Nico และดูเหมือนว่าจะทำได้ดี
  • นอกจากนี้ยังมี ผู้ร่วมพัฒนา Ninja อีกมากที่เข้าร่วมโปรเจกต์

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

 
GN⁺ 2024-11-30
ความคิดเห็นบน Hacker News
  • ประโยคที่ว่า “การเขียนโปรแกรมมักถูกพูดถึงเหมือนเป็นการเขียนโค้ด แต่จริง ๆ แล้ว สถาปัตยกรรม สำคัญกว่าโค้ด และ ประเด็นทางสังคม ก็สำคัญกว่าสถาปัตยกรรมขึ้นไปอีก” ถ่ายทอดความคิดที่อยู่ในใจมานานได้อย่างตรงเป๊ะ

    • เห็นด้วยอย่างยิ่ง Peopleware 1987 [1] ก็พูดเรื่องเดียวกัน
      บทที่ 1 ของหนังสือเสนอว่า “ปัญหาหลักของงานเราไม่ใช่เรื่องเทคนิคเท่าไร แต่เป็นเรื่องเชิงสังคมวิทยามากกว่า” และพูดถึงประเด็นทางสังคม/การเมือง เช่น เคมีและความผูกพันของทีม, “เวลาโฟกัส”, สภาพแวดล้อมการทำงานที่เงียบ และต้นทุนการลาออก
      [1] https://en.wikipedia.org/wiki/Peopleware:_Productive_Project...
    • การเขียนโปรแกรมและเครื่องมือของมันมีไว้เพื่อ คน ไม่ใช่เพื่อคอมพิวเตอร์ และนอกจากงานเล็กน้อยมาก ๆ แล้ว การสร้างสิ่งใดก็ตามที่ทำงานบนคอมพิวเตอร์จำเป็นต้องมีคนหลายคน
      ดังนั้นจึงมองว่าวิศวกรรมซอฟต์แวร์ใกล้เคียงกับ สังคมศาสตร์ มากกว่าที่หลายคนยอมรับหรือรู้สึกสบายใจ สังคมศาสตร์มีความไหลลื่นและคาดเดาได้น้อยกว่าวิทยาศาสตร์ธรรมชาติ จึงดูเหมือนมีแนวโน้มที่จะหลีกเลี่ยงส่วนที่เป็นสังคม หรือจัดการกับมันแบบดิบ ๆ มาก ในขณะที่หมกมุ่นกับรายละเอียดเชิงอะตอมของเทคโนโลยี แต่ผลิตภัณฑ์ที่ทีมสร้างออกมากลับแย่มากบ่อยเกินไป
    • https://en.wikipedia.org/wiki/Conway's_law
      “องค์กรที่ออกแบบระบบย่อมถูกบังคับให้สร้างแบบออกแบบที่จำลองโครงสร้างการสื่อสารขององค์กรนั้น” — Melvin E. Conway, How Do Committees Invent?
    • จากประสบการณ์ของผม ปัญหาทางเทคนิคประมาณ 80% เกิดจากคนสองคนหรือสองทีมไม่อยากนั่งคุยกันตรง ๆ
    • แต่นั่นไม่ได้แปลว่าเป็นการแบ่งขั้วแบบขาวดำ สถาปัตยกรรมที่ดีตอบสนองสิ่งที่ผู้คนต้องการจากระบบได้ พร้อมกับยังคงทำให้ มนุษย์เข้าใจได้
  • ตอนนั้นก็มีการถกเถียงกันแล้ว:
    The Success and Failure of Ninja - https://news.ycombinator.com/item?id=23157783 - พฤษภาคม 2020, 38 ความคิดเห็น
    ผ่านไปประมาณ 1 ปี การโพสต์ซ้ำก็ถือว่าโอเค และลิงก์เธรดเก่าไว้สำหรับผู้อ่านที่อยากอ่านเพิ่ม

  • ส่วนที่บอกว่า “Android ใช้ Ninja กับคอมโพเนนต์ใหญ่บางอย่างของระบบ แต่ไม่เคยเข้าใจแน่ชัดว่าเป็นอะไร” นั้นตลกดี
    จริง ๆ แล้ว Ninja มีบทบาทใหญ่มากใน AOSP ระบบบิลด์ช่วงแรกใช้ Makefile แต่ซับซ้อนขึ้นอย่างรวดเร็วเพราะ soong ซึ่งเป็นระบบบิลด์แบบประกาศที่ทำเอง และการย้ายไป Bazel ที่ล้มเหลว/หยุดชะงัก Google สร้าง kati(https://github.com/google/kati) เพื่อแปลง Makefile เป็นไฟล์บิลด์ของ Ninja และผลลัพธ์ก็ใหญ่มหึมาจริง ๆ:
    λ wc -l out/build-qssi.ninja
    3035442 out/build-qssi.ninja
    กระบวนการเปลี่ยนจาก Makefile/soong ไปเป็น Ninja นั้นเจ็บปวดและใช้เวลาหลายนาทีแม้บนเครื่องรุ่นใหม่ แต่พอ Ninja รับช่วงต่อแล้ว มันก็รันได้เร็วมาก

    • ไม่เคยใช้ Ninja มาก่อน เลยสงสัยว่ามีข้อดีอะไรเมื่อเทียบกับ Makefile
      ยังสงสัยด้วยว่าคุ้มหรือไม่ที่จะเพิ่มเครื่องมืออีกตัวเพื่อแปลจากเครื่องมือหนึ่งไปยังอีกเครื่องมือหนึ่ง โดยเฉพาะถ้าไฟล์ Ninja ใหญ่ขนาดนั้นและมนุษย์อ่านยาก
  • ในส่วนที่ว่า “ผมเชื่อว่าโปรแกรมเมอร์รับรู้ latency ได้ และแม้จะไม่สังเกตตัวเองก็ได้รับผลต่ออารมณ์ Google เพิ่งทำงานวิจัยด้านนี้และช่วยยืนยันความเชื่อของผมอยู่บ้าง” อยากรู้ว่า งานวิจัยเรื่อง latency นั้นเผยแพร่จริงหรือเปล่า

    • ไม่แน่ใจว่าเป็นงานวิจัยที่ผู้เขียนหมายถึงตรง ๆ หรือไม่ แต่อาจเป็นบทความนี้: https://www.computer.org/csdl/magazine/so/2023/04/10176199/1...
    • 400ms Doherty Threshold ใช้กับการบิลด์ได้ด้วย
  • Ninja ค่อนข้างได้รับความนิยมในหมู่ นักพัฒนาเกม
    ประโยคที่ว่า “Windows ยังคงเป็นแพลตฟอร์มขนาดใหญ่ในฝั่งนักพัฒนา และนักพัฒนาเหล่านั้นก็หิวโหยเครื่องมือ” น่าสนใจดี จากมุมมองของคนที่พัฒนาบน Windows เป็นหลัก กลับรู้สึกว่านักพัฒนา Linux ที่ต้องอยู่โดยไม่มี debugger ดี ๆ อย่าง Visual Studio หรือ profiler อย่าง Superluminal น่าจะเป็นฝ่ายที่หิวโหยเครื่องมือมากกว่า ช่วงหลังช่องว่างระหว่างสองแพลตฟอร์มดูเหมือนจะแคบลงเรื่อย ๆ และโดยรวมก็ชอบยูทิลิตี Rust ที่ทำงานข้ามแพลตฟอร์มได้

    • ทุกครั้งที่มีคนเอา Visual Studio กับคำว่า “ดี” มาไว้ในประโยคเดียวกัน ผมจะรู้สึกแปลก ๆ ว่าจักรวาลนี้เป็นซิมูเลชัน และคนอื่น ๆ มาจากคนละจักรวาล
      นึกไม่ออกว่าการโต้ตอบใด ๆ กับ Visual Studio จะเรียกว่า “ดี” ได้บ้าง พอนึกออกอยู่สองสามอย่างที่เรียกได้ว่า “พอใช้ได้แบบเฉียดฉิว” แต่การดีบักไม่อยู่ในนั้น อย่างไรก็ตามในเวอร์ชัน 2022 debugger ไม่ได้เต็มไปด้วยบั๊กอีกต่อไปแล้ว บางทีอาจหมายถึงเรื่องนั้นก็ได้
    • ไม่รู้ว่าในโลกแบบไหนที่ Visual Studio debugger ถูกมองว่าดี เว้นแต่ว่าในที่สุดจะแก้แล้ว ครั้งล่าสุดที่ใช้ มันช้าจนทนไม่ได้ ถึงขั้น step ทีละบรรทัดใช้เวลาหลายวินาที
    • มีชุมชนที่มองว่า UNIX คือทั้งหมดของเครื่องมือสำหรับนักพัฒนา และดูเหมือนว่าพวกเขาจะเห็นแต่ป่าแต่พลาดต้นไม้ไป
      ผมรู้จัก UNIX ดีพอสมควร เพราะใช้หลายสายพันธุ์มาตั้งแต่ได้สัมผัส Xenix ในปี 1993 แต่แทบไม่ใช้ WSL เลยนอกจากไว้รันคอนเทนเนอร์ Linux Docker
  • บางอย่างที่ยังใช้ Ninja อยู่ ผมเปลี่ยนไปเป็น samurai แล้ว และมันดีขึ้นในทุกด้านที่เป็นไปได้
    ถึงอย่างนั้น ผมก็ยังคิดว่าระบบบิลด์ประเภทนี้เดินผิดทาง สิ่งที่ต้องการจากระบบบิลด์คือการแฮชเนื้อหาของอินพุตแบบถ่ายทอดทั้งหมด แล้วตรวจสอบว่าผลลัพธ์นั้นมีอยู่ในรีจิสทรีหรือไม่

    • ใช่เลย ระบบบิลด์ที่รองรับแคชแบบกระจาย โดยพื้นฐานแล้วใช้ ไดเจสต์ แทนไทม์สแตมป์ในการตรวจจับการเปลี่ยนแปลง: Bazel, Pants, Buck และอื่น ๆ ก็เป็นแบบนั้น
      เพียงแต่ว่าทั้งหมดนั้นซับซ้อนมาก ถ้ามองเฉพาะการบิลด์ในเครื่อง ผมเข้าใจว่า SCons กับ Waf ก็ใช้แฮชในการตรวจจับการเปลี่ยนแปลงเช่นกัน
    • คุณอาจสนใจ n2 ที่ผู้เขียน Ninja ทำไว้
    • นั่นน่าจะเป็นไอเดียของ NetKernel
      ผมเคยทำสิ่งที่คล้ายกันเป็นไลบรารี Deno ชื่อ “TDAR”[1] และมันทำงานได้ดี แต่ถ้าจะห่อหุ้มเครื่องมือบรรทัดคำสั่งที่ถือว่าตัวเองทำงานบนไฟล์ซิสเต็มที่แก้ไขได้ ให้ดูราวกับเรียกใช้เป็นฟังก์ชันบริสุทธิ์ ก็ต้องลงแรงอยู่พอสมควร
      [1] ยังไม่ได้แยกออกมาจากโปรเจกต์แม่[2] แต่เคยพูดถึงไว้ในวิดีโอ YouTube นี้: https://youtu.be/sty29o8sUKI
      [2] ถ้าสนใจเรื่องนี้ จะมากระตุ้นให้เปิดซอร์สก็ได้ togos zero zero at gee mail dot comb
    • อยากรู้ว่า Samurai ดีกว่าตรงไหน ผมนึกว่ามันเป็นซับเซตที่เข้ากันได้กับ Ninja
      แล้ว “ไม่ใช่สิ่งที่ผมต้องการ” ก็ไม่ได้แปลว่า “ผิด” เพราะในโลกนี้มีคนที่มีรสนิยมต่างกันอยู่
    • ไม่แน่ใจว่า Samurai ยังมีชีวิตอยู่ไหม ผมส่ง pull request ที่ปรับปรุง การจัดการสัญญาณ ไป แต่ถูกปล่อยทิ้งไว้นานเกินครึ่งปีแล้ว
  • ถ้าจะใช้ โมดูล C++20 ใน CMake ต้องมี Ninja ดังนั้น Ninja น่าจะยังอยู่ไปอีกนานทีเดียว

  • ส่วนที่น่าสนใจที่สุดคือประโยคที่ว่า “มักต้องประนีประนอมระหว่างความถูกต้องกับความสะดวกหรือประสิทธิภาพ และต้องตระหนักว่าตัวเองเลือกอยู่ตรงไหนบนเส้นต่อเนื่องนั้น”
    โปรแกรมเมอร์บางคนมองพลวัตนี้อย่างแข็งทื่อเกินไป และคิดว่าคุณค่าฝั่งหนึ่งย่อมต้องมาก่อนโดยปริยาย แต่ในความเป็นจริง ปฏิสัมพันธ์ระหว่างสิ่งเหล่านี้ละเอียดอ่อนกว่ามาก เช่น เครื่องมือที่ยอมเสียความถูกต้องไปบางส่วนเพื่อแลกกับความสะดวก อาจสร้างผลลัพธ์ที่ถูกต้องกว่าในภาพรวมของ ecosystem ได้ เมื่อเทียบกับทางเลือกที่ถูกต้องกว่าแต่สะดวกน้อยกว่า หากโปรแกรมเมอร์หลีกเลี่ยงทางเลือกหลัง

    • นี่เป็นขุมทองของ insight จริง ๆ นั่นแหละคือเหตุผลที่ Python, Go, TypeScript/JavaScript ได้รับความนิยมมากกว่า Haskell/OCaml อย่างมาก
  • “ขออภัยสำหรับชื่อที่น่าอาย” งั้นเหรอ ชื่อนี้ยอดเยี่ยมออก
    ป.ล. ถ้า implement สิ่งนี้ได้ อาจทำให้เร็วขึ้นได้อีก: https://github.com/ninja-build/ninja/issues/2157 แต่ตามที่บทความอธิบายไว้ เครื่องมือนี้ จงใจไม่เก็บสถานะ แม้กระทั่ง hint เล็ก ๆ น้อย ๆ จากการรันครั้งก่อน