ปัญหาด้านประสิทธิภาพที่เกิดขึ้นมานานของผู้ใช้ Emacs บน macOS ได้ถูกติดตามไปจนถึงสาเหตุหลักแล้ว ข้อผิดพลาดนี้แสดงออกมาในรูปของการใช้หน่วยความจำที่เพิ่มขึ้นเรื่อยๆ และระบบค้างในที่สุด โดยเฉพาะอย่างยิ่งส่งผลกระทบต่อผู้ใช้ที่มีฮาร์ดแวร์ที่เร็วกว่าและจอแสดงผล high-DPI หลังจากหลายปีของการร้องเรียนและความพยายามในการแก้ไขข้อผิดพลาดที่ล้มเหลว การสืบสวนโดยละเอียดได้เผยให้เห็นว่าตัวการอยู่ลึกลงไปในการใช้งาน GUI เฉพาะ macOS
ความขัดแย้งของประสิทธิภาพ: Mac ที่เร็วกว่าทำให้ Emacs ช้าลง
การสืบสวนได้เปิดเผยปัญหาที่ขัดกับสัญชาตญาณที่ฮาร์ดแวร์ Mac ที่มีประสิทธิภาพมากขึ้นกลับทำให้ประสิทธิภาพของ Emacs แย่ลง ปัญหานี้เกิดจากวิธีที่ Emacs จัดการกับ GUI events ผ่าน [NSApp run]
invocations ภายใน main event loop ทุกครั้งที่เกิดขึ้น มันจะสร้าง graphical context ที่สมบูรณ์ตั้งแต่ต้น - การเริ่มต้น windows การโหลด fonts และ glyphs และการวาด frames - เพียงเพื่อจะทำลายทุกอย่างลงในเวลาไม่กี่มิลลิวินาทีหลังจากการประมวลผล event เสร็จสิ้น
บนเครื่องที่เร็วกว่า วงจรนี้เกิดขึ้นหลายพันหรือแม้กระทั่งหลายล้านครั้งในระหว่างการดำเนินงานง่ายๆ เช่น การปรับขนาดหน้าต่าง ฮาร์ดแวร์ที่เร็วกว่าสามารถประมวลผล events ได้ยิ่งมีวงจรการจัดสรรและการยกเลิกการจัดสรรเกิดขึ้นมากขึ้น จอแสดงผล High-DPI ทำให้ปัญหาซับซ้อนขึ้นโดยต้องการการจัดสรรหน่วยความจำที่ใหญ่กว่าสำหรับ rendering surfaces
NSApp run: การเรียก macOS API ที่เริ่มต้น main application event loop ประมวลผล system events ที่รอดำเนินการทั้งหมดก่อนที่จะส่งคืนการควบคุมไปยังโค้ดที่เรียก
สถาปัตยกรรม Threading ของ Emacs บน macOS:
- Main thread
- EventDispatch thread
- File descriptor handler thread
- การพึ่งพากลไกการล็อกอย่างหนัก
- การประมวลผลแบบ infinite loop สำหรับการจัดการ event
การจัดการหน่วยความจำที่ผิดพลาด
การสร้างและทำลาย GUI resources อย่างรวดเร็วสร้างปัญหาการจัดการหน่วยความจำที่เกิดขึ้นเป็นลูกโซ่ ในขณะที่ Emacs จัดการกับการดำเนินการ cleanup ส่วนใหญ่อย่างมีประสิทธิภาพ การจัดสรรบางส่วนหลุดรอดผ่านช่องว่าง Menu items, font glyphs และสำเนา frame state สะสมในหน่วยความจำเพราะ core Emacs engine ไม่คาดหวัง phantom instances เหล่านี้ที่สร้างโดย GUI layer
ทำให้เรื่องแย่ลงไปอีก macOS เริ่มแคช objects ที่จัดสรรบ่อยเหล่านี้ โดยเข้าใจผิดว่าเป็นข้อมูลสำคัญ สิ่งนี้ผลักดันเนื้อหา Emacs จริง - ตัวแปร ผลการค้นหา และข้อมูลผู้ใช้ - ให้อยู่ไกลออกไปในลำดับความสำคัญของหน่วยความจำ ชุมชนได้พัฒนาวิธีแก้ไขชั่วคราว แต่สถาปัตยกรรมพื้นฐานยังคงมีปัญหา
โค้ดทดสอบ Memory Leak:
(dotimes (x 10000)
(let ((frame (make-frame-command)))
(sleep-for 0.01)
(delete-frame frame)))
โค้ดตัวอย่างนี้แสดงให้เห็นปัญหา memory leak โดยการสร้างและลบเฟรมอย่างรวดเร็ว ทำให้การใช้หน่วยความจำเพิ่มขึ้นอย่างต่อเนื่อง
ชุมชนแสวงหาทางเลือกท่ามกลางความหงุดหงิด
ปัญหาด้านประสิทธิภาพได้ผลักดันให้ผู้ใช้ Emacs มานานหลายคนพิจารณาทางเลือกอื่นหรือวิธีแก้ไขชั่วคราว บางคนได้เปลี่ยนไปใช้ Emacs ในโหมด terminal เท่านั้น เสียสละคุณสมบัติ GUI เพื่อความเสถียรและประสิทธิภาพ คนอื่นๆ กำลังสำรวจ editors ที่แตกต่างกันโดยสิ้นเชิง โดย Helix และ NeoVim ถูกกล่าวถึงบ่อยครั้งว่าเป็นตัวเลือกทดแทนที่เป็นไปได้
ความกระตุกของ Emacs บน macOS ได้ฆ่าฉันอย่างช้าๆ มากพอที่ฉันกำลังคิดที่จะเปลี่ยนไปใช้อย่างอื่นโดยสิ้นเชิงหลังจากใช้ emacs มาเกือบสิบปี
สถานการณ์นี้ได้สร้าง ecosystem ที่เจริญรุ่งเรืองของ Emacs builds และ forks ทางเลือกโดยเฉพาะสำหรับ macOS รวมถึง Emacs Doom, Homebrew Emacs-Plus และ emacs-mac ของ Mitsuharu Yamamoto ทางเลือกเหล่านี้มักให้ประสิทธิภาพที่ดีกว่าแต่ต้องการให้ผู้ใช้ดูแลการติดตั้งแยกต่างหาก
การสร้าง Emacs ทางเลือกสำหรับ macOS:
- Emacs Doom
- MacPorts Emacs
- Homebrew Emacs-Plus
- Mitsuharu Yamamoto's emacs-mac
- Aquamacs
- PGTK frontend (รองรับ Wayland โดยตรง สามารถปรับใช้กับ macOS ได้)
โซลูชันที่เป็นไปได้ในอนาคต
การอภิปรายกำลังดำเนินอยู่ในชุมชนนักพัฒนา Emacs เกี่ยวกับการแก้ไขที่เป็นไปได้ แม้ว่าโซลูชันจะไม่ง่าย การใช้งานปัจจุบันใช้เพียงสาม threads และพึ่งพา locking mechanisms เป็นหลัก การแก้ไขที่เหมาะสมจะต้องการการเปลี่ยนแปลงทางสถาปัตยกรรมที่สำคัญต่อการจัดการ event และการสนับสนุน threading
แนวทางหนึ่งที่เสนอเกี่ยวข้องกับการย้าย macOS-specific code ไป Swift อย่างค่อยเป็นค่อยไป ซึ่งให้การจัดการหน่วยความจำที่ดีกว่า การสนับสนุน asynchronous ในตัว และคุณสมบัติ thread-safety สิ่งนี้อาจขจัด locking mechanisms ที่ซับซ้อนและให้การจัดการทรัพยากรที่มีประสิทธิภาพมากขึ้น แม้ว่าการเปลี่ยนแปลงดังกล่าวจะเป็นงานใหญ่
การสืบสวนนี้เน้นย้ำถึงความท้าทายที่กว้างขึ้นที่ applications ข้ามแพลตฟอร์มต้องเผชิญ: การสร้างสมดุลระหว่างความต้องการของ operating system paradigms ที่แตกต่างกันในขณะที่รักษาฟังก์ชันการทำงานและประสิทธิภาพที่สม่ำเสมอในทุกแพลตฟอร์มที่สนับสนุน
อ้างอิง: Emacs: The MacOS Bug