ระบบปลั๊กอินคือภาษีด้านประสิทธิภาพ
This dated article records the Perry version and measurements available when it was published. Use the current limitations, platform guide, and benchmark artifact for decisions today.
คุณติดตั้ง VS Code มันเร็ว คุณเพิ่มส่วนขยาย 15 ตัว ตอนนี้ใช้เวลา 4 วินาทีในการเริ่มต้นและ Extension Host กิน RAM 800 MB เกิดอะไรขึ้น?
รูปแบบนี้ซ้ำแล้วซ้ำเล่าทุกที่: WordPress, Eclipse, Chrome, Figma, Slack แอปส่งมาเร็ว ปลั๊กอินทำให้มันช้า ไม่มีใครแปลกใจอีกแล้ว — เรายอมรับว่ามันเป็นต้นทุนของความสามารถในการขยาย
แต่ระบบปลั๊กอินไม่ใช่แค่ปัญหาประสิทธิภาพ มันเป็นปัญหาปรัชญาการออกแบบ อุตสาหกรรมสับสนระหว่าง "ความสามารถในการขยาย" กับ "ไดนามิซึมในรันไทม์" ในขณะที่คำตอบที่ดีกว่ามักเป็นการผสมรวมในเวลาคอมไพล์ ปลั๊กอินเดียวที่มีประสิทธิภาพคือปลั๊กอินที่หยุดเป็นปลั๊กอินในเวลาคอมไพล์
สเปกตรัมประสิทธิภาพของความสามารถในการขยาย
ไม่ใช่ทุกความสามารถในการขยายที่มีต้นทุนเท่ากัน มีสเปกตรัมจากศูนย์ต้นทุนถึงต้นทุนสูงสุด และอุตสาหกรรมส่วนใหญ่ตั้งอยู่ที่ปลายที่แพง:
- การลิงก์แบบสแตติก / โมดูลในเวลาคอมไพล์ — ไม่มีโอเวอร์เฮด ไลบรารี C, Rust crates, แพ็กเกจ Go ขอบเขตโมดูลหายไปทั้งหมดในไบนารีสุดท้าย
- ไลบรารีที่แชร์โหลดเมื่อเริ่มต้น — แทบไม่มี โมดูล nginx, โมดูลเคอร์เนล Linux ต้นทุนครั้งเดียวเมื่อโหลด จากนั้นเป็นการเรียกฟังก์ชันโดยตรง
- การ dispatch แบบไดนามิกผ่านอินเทอร์เฟซ / vtables — โอเวอร์เฮดน้อย ปลั๊กอินเอนจินเกมใน C++ การ indirection ของตัวชี้หนึ่งครั้งต่อการเรียก
- ปลั๊กอินที่ตีความในโปรเซสเดียวกัน — ปานกลาง ปลั๊กอิน PHP ของ WordPress, OSGi bundles ของ Eclipse ทุกการเรียกปลั๊กอินผ่านตัวตีความ
- ปลั๊กอินในโปรเซสแยกผ่าน IPC — มาก ส่วนขยาย VS Code, ส่วนขยาย Chrome ทุกการโต้ตอบข้ามขอบเขตโปรเซสและซีเรียลไลซ์ข้อมูล
- ปลั๊กอินใน sandbox ผ่าน IPC ที่ซีเรียลไลซ์ — หนัก ปลั๊กอิน Figma, content scripts ของส่วนขยายเบราว์เซอร์ การซีเรียลไลซ์ การดีซีเรียลไลซ์ และการบังคับใช้ sandbox ในทุกการเรียก
ข้อมูลเชิงลึกสำคัญ: ปลั๊กอินเดียวที่มีประสิทธิภาพคือปลั๊กอินที่หยุดเป็นปลั๊กอินในเวลาคอมไพล์ ระดับ 1 และ 2 เร็วเพราะ "ปลั๊กอิน" กลายเป็นสิ่งที่แยกแยะไม่ออกจากโค้ดโฮสต์ในอาร์ติแฟกต์สุดท้าย
ความเสียหายในโลกจริง
WordPress
ทุกปลั๊กอินเชื่อมต่อกับวงจรชีวิตของคำขอ 30 ปลั๊กอินหมายถึง 30 ชั้นของการเรียกฟังก์ชันต่อการโหลดหน้า ผลลัพธ์: ปลั๊กอิน caching มีอยู่เพียงเพื่อบรรเทาความเสียหายของปลั๊กอินอื่น ปลั๊กอินประสิทธิภาพเพื่อแก้ปัญหาประสิทธิภาพที่ปลั๊กอินสร้างขึ้น ความย้อนแย้งเขียนตัวเอง
VS Code
ส่วนขยายแชร์ event loop Node.js เดียวในโปรเซสแยก ส่วนขยายที่ทำงานผิดหนึ่งตัวบล็อกทั้งหมด Extension Host มักปรากฏเป็นตัวใช้ CPU อันดับต้นๆ บนเครื่องของนักพัฒนา Microsoft ได้สร้างเครื่องมือ profiling คำสั่ง bisect และระบบเหตุการณ์การเปิดใช้งาน — โครงสร้างพื้นฐานทั้งหมดเพื่อจัดการปัญหาที่ส่วนขยายสร้างขึ้น
Eclipse
เรื่องเตือนใจ การแก้ไข bundle OSGi โอเวอร์เฮดการโหลดคลาส กราฟ dependency ขนาดใหญ่ ครั้งหนึ่งเคยเป็น IDE ที่ได้รับความนิยมมากที่สุด ตอนนี้ถูกทิ้งโดยนักพัฒนากระแสหลักเป็นส่วนใหญ่ สถาปัตยกรรมปลั๊กอินที่ควรจะเป็นจุดแข็งที่ยิ่งใหญ่ที่สุดกลับกลายเป็นจุดอ่อนที่กำหนดตัวตน
ตัว Electron เอง
ปัญหาปลั๊กอินในระดับแพลตฟอร์ม ทุกแอป Electron ส่งรันไทม์ Chromium + Node.js เต็มรูปแบบ VS Code คือ Electron Slack คือ Electron Discord คือ Electron แต่ละตัวใช้ RAM 300–500 MB อย่างอิสระเพื่อเรนเดอร์สิ่งที่เป็นหน้าต่างแชทหรือเท็กซ์เอดิเตอร์โดยพื้นฐาน "ปลั๊กอิน" ที่นี่คือแพลตฟอร์มเว็บทั้งหมด บันเดิลใหม่สำหรับทุกแอปพลิเคชัน
ทำไมอุตสาหกรรมยังคงเลือกปลั๊กอิน
ถ้าปลั๊กอินแพงขนาดนั้น ทำไมทุกคนยังคงสร้างพวกมัน? เหตุผลส่วนใหญ่เป็นเรื่ององค์กร ไม่ใช่เทคนิค:
- ประสบการณ์นักพัฒนา — ปลั๊กอินเขียนง่ายเมื่อคุณไม่สนใจเรื่องประสิทธิภาพ ส่งไฟล์ JS เชื่อมต่อกับอีเวนต์บางอย่าง เสร็จ
- การเติบโตของระบบนิเวศ — ปลั๊กอินสร้างผลกระทบเครือข่ายและการมีส่วนร่วมของชุมชน marketplace ที่มี 30,000 ส่วนขยายเป็นคูน้ำที่ทรงพลัง
- ความสะดวกขององค์กร — ปลั๊กอินให้ทีมเลื่อนการตัดสินใจออกแบบ "มีคนจะเขียนปลั๊กอินสำหรับสิ่งนั้น" เป็นสถาปัตยกรรมที่เทียบเท่ากับ "เราจะแก้ไขในขั้นตอนหลังการผลิต"
- โมเดลธุรกิจ — marketplace ปลั๊กอินสร้างรายได้และ lock-in แพลตฟอร์มจับมูลค่าจากระบบนิเวศ
ความจริงที่ไม่สบายใจ: ปลั๊กอินมักเป็นวิธีหลีกเลี่ยงการตัดสินใจสถาปัตยกรรมที่ยากเกี่ยวกับสิ่งที่ควรอยู่ในแกนกลาง พวกมันให้คุณส่งบางสิ่งที่ไม่สมบูรณ์และเรียกมันว่า "ขยายได้"
ทางเลือก: การผสมรวมในเวลาคอมไพล์
จะเป็นอย่างไรถ้าความสามารถในการขยายเกิดขึ้นในเวลา build แทนที่จะเป็นรันไทม์?
นี่ไม่ใช่สมมติฐาน มีตัวอย่างที่พิสูจน์แล้วในภาษาระบบ:
- Rust proc macros — โค้ดที่รันในเวลาคอมไพล์และสร้างโค้ดเนทีฟที่ไม่มีโอเวอร์เฮด การซีเรียลไลซ์ Serde, การตั้งค่ารันไทม์ Tokio, การเราต์ Axum — ทั้งหมดแก้ไขก่อนที่โปรแกรมของคุณจะเริ่ม
- Zig comptime — การรันในเวลาคอมไพล์ที่กำจัดการแยกสาขาในรันไทม์ทั้งหมด โครงสร้างข้อมูลเจเนอริกถูกมอโนมอร์ฟไอซ์ การกำหนดค่าถูกแก้ไข โค้ดที่ตายถูกกำจัด สิ่งที่เหลือคือสิ่งที่รันจริง
- C++ templates / constexpr — โพลีมอร์ฟิซึมในเวลาคอมไพล์ที่ไม่มีต้นทุนในรันไทม์ STL บรรลุประสิทธิภาพที่ยอดเยี่ยมเพราะทุกอัลกอริทึมเจเนอริกเฉพาะทางในเวลาคอมไพล์
- Tree-shaking ใน bundlers — เวอร์ชันบางส่วนที่ไม่สมบูรณ์แบบของแนวคิดนี้ที่นำไปใช้กับ JavaScript Webpack และ Rollup กำจัด exports ที่ไม่ได้ใช้ในเวลา build ข้อจำกัดคือมันทำได้แค่ลบโค้ด ไม่ใช่เฉพาะทาง
รูปแบบสอดคล้องกัน: ย้ายการตัดสินใจจากรันไทม์ไปเวลา build สิ่งที่คุณไม่รวมไม่มีต้นทุน สิ่งที่คุณรวมคอมไพล์เป็นโค้ดเนทีฟโดยไม่มีการ indirection ขอบเขตโมดูลกลายเป็นเครื่องมือจัดระเบียบระดับซอร์สโค้ด ไม่ใช่ขอบเขตประสิทธิภาพในรันไทม์
สิ่งนี้หมายความว่าอย่างไรสำหรับ TypeScript
TypeScript เป็นภาษาที่ได้รับความนิยมมากที่สุดสำหรับสร้างเครื่องมือที่ขยายได้ — และแย่ที่สุดด้านประสิทธิภาพรันไทม์ ระบบนิเวศ TypeScript ทั้งหมดรันบน Node.js ซึ่งรันบน V8 ซึ่ง JIT-คอมไพล์ JavaScript ทุกเลเยอร์เพิ่มโอเวอร์เฮด: เวลาอุ่น JIT, การหยุดเก็บขยะ, dynamic dispatch สำหรับทุกการเข้าถึงคุณสมบัติ, ขอบเขต IPC ระหว่างโปรเซส
นี่คือจุดที่ Perry เข้ามา Perry คอมไพล์ TypeScript โดยตรงเป็นไบนารีเนทีฟ ไม่มี V8 ไม่มีการอุ่น JIT ไม่มีการหยุดเก็บขยะ ไม่มีขอบเขต IPC
เมื่อโมดูลของคุณคอมไพล์เป็นโค้ดเนทีฟ "ปลั๊กอิน" กลายเป็นแค่... โมดูล มันผสมรวมในเวลา build ไบนารีสุดท้ายมีโอเวอร์เฮดปลั๊กอินเป็นศูนย์เพราะไม่มีปลั๊กอิน — แค่โค้ดเนทีฟ ตัวจัดการเส้นทาง Express, ฟังก์ชัน middleware, ไลบรารียูทิลิตี้ — ทั้งหมดคอมไพล์เป็นการเรียกฟังก์ชันโดยตรงในไบนารีเดียวกัน ไม่มีการโหลดแบบไดนามิก ไม่มีการซีเรียลไลซ์ ไม่มีขอบเขตโปรเซส
# Your app, your dependencies, your "plugins" — one binary
$ perry compile server.ts -o server
Compiling server.ts + 43 modules...
✓ Built executable: server (1.8 MB, 0.7s)
$ ./server
Listening on port 3000
นี่ไม่ใช่ทฤษฎี Perry คอมไพล์เฟรมเวิร์ก TypeScript จากโลกจริงแล้ว — Hono, tRPC, Strapi — เป็นไบนารีเนทีฟ ARM64 ขนาดต่ำกว่า 2 MB ในเวลาน้อยกว่าหนึ่งวินาที โมดูลที่ประกอบเป็นเฟรมเวิร์กเหล่านั้นถูกคอมไพล์ ลิงก์ และอินไลน์เข้าไปในไฟล์เรียกทำงานเดียว สิ่งที่จะเป็นสถาปัตยกรรมปลั๊กอินที่มีโอเวอร์เฮดรันไทม์ใน Node.js กลายเป็นการผสมรวมที่ไม่มีต้นทุนในไบนารี Perry
ความสามารถในการขยายที่คุณต้องการจริงๆ
การคัดค้านนั้นชัดเจน: "แต่ฉันต้องการความสามารถในการขยายในรันไทม์ ผู้ใช้ต้องติดตั้งปลั๊กอินโดยไม่ต้องคอมไพล์ใหม่"
จริงหรือ? สำหรับแอปพลิเคชันส่วนใหญ่ ชุดส่วนขยายเป็นที่รู้จักในเวลา build คุณเลือก middleware Express, ไดรเวอร์ฐานข้อมูล, ไลบรารี auth, เฟรมเวิร์กการ logging ของคุณ — แล้วก็ deploy "ความสามารถในการขยาย" อยู่ใน package.json ของคุณ แก้ไขเมื่อ npm install ไม่ใช่ในรันไทม์
แอปพลิเคชันที่ต้องการการโหลดปลั๊กอินในรันไทม์จริงๆ — VS Code, WordPress, เบราว์เซอร์ — เป็นข้อยกเว้น ไม่ใช่กฎ และแม้แต่สิ่งเหล่านั้นก็จ่ายราคาสูงสำหรับมัน สำหรับทุกอย่างอื่น การผสมรวมในเวลาคอมไพล์ให้ความยืดหยุ่นเดียวกันโดยไม่มีโอเวอร์เฮด
ความแตกต่างคือความซื่อสัตย์ทางสถาปัตยกรรม แทนที่จะแกล้งทำเป็นว่าทุกแอปพลิเคชันต้องการระบบปลั๊กอิน คุณถาม: ความสามารถในการขยายนี้ต้องเกิดขึ้นในรันไทม์ หรือคอมไพเลอร์สามารถทำงานได้?
เส้นทางข้างหน้า
การติดสถาปัตยกรรมปลั๊กอินของอุตสาหกรรมเป็นอาการของการยอมรับโอเวอร์เฮดรันไทม์ว่าหลีกเลี่ยงไม่ได้ มันไม่ใช่ คอมไพเลอร์สามารถทำงานได้ การผสมรวมในเวลา build ให้ความสามารถในการขยายโดยไม่มีภาษี
เราสร้าง Perry เพราะเราเชื่อว่านักพัฒนา TypeScript สมควรได้รับประสิทธิภาพเนทีฟโดยไม่ต้องสละภาษาที่พวกเขารัก โมดูลของคุณควรผสมรวมในเวลา build คอมไพล์เป็นการเรียกฟังก์ชันโดยตรง และรันโดยไม่มีโอเวอร์เฮดของรันไทม์ที่มีอยู่เพียงเพื่อทำให้ "ความสามารถในการขยาย" เป็นไปได้
ระบบปลั๊กอินที่เร็วที่สุดคือระบบที่ไม่มีอยู่ในรันไทม์
ชอบโพสต์นี้ใช่ไหม? รับโพสต์ถัดไป
บันทึกสั้น ๆ เกี่ยวกับการอัปเดตของ Perry และสิ่งที่เรากำลังสร้างต่อไป
Your email is sent to Resend for the Perry newsletter. You can request unsubscribe or deletion at any time. Privacy notice