Một vấn đề hiệu suất tồn tại lâu dài đã gây khó khăn cho người dùng Emacs trên macOS cuối cùng đã được truy ra nguyên nhân gốc rễ. Lỗi này biểu hiện qua việc sử dụng bộ nhớ tăng dần và cuối cùng dẫn đến treo hệ thống, đặc biệt ảnh hưởng đến người dùng có phần cứng nhanh hơn và màn hình độ phân giải cao. Sau nhiều năm phàn nàn và những nỗ lực gỡ lỗi thất bại, một cuộc điều tra chi tiết đã tiết lộ thủ phạm nằm sâu trong phần triển khai GUI dành riêng cho macOS.
Nghịch lý về hiệu suất: Mac nhanh hơn làm cho Emacs chậm hơn
Cuộc điều tra đã phát hiện ra một vấn đề phản trực giác khi phần cứng Mac mạnh hơn thực sự làm hiệu suất Emacs tệ hơn. Vấn đề bắt nguồn từ cách Emacs xử lý các sự kiện GUI thông qua việc gọi [NSApp run]
trong vòng lặp sự kiện chính. Mỗi khi điều này xảy ra, nó tạo ra một bối cảnh đồ họa hoàn chỉnh từ đầu - khởi tạo cửa sổ, tải phông chữ và ký tự, và vẽ khung - chỉ để phá bỏ mọi thứ vài mili giây sau đó khi việc xử lý sự kiện hoàn tất.
Trên các máy nhanh hơn, chu kỳ này xảy ra hàng nghìn hoặc thậm chí hàng triệu lần trong các thao tác đơn giản như thay đổi kích thước cửa sổ. Phần cứng càng nhanh có thể xử lý sự kiện, càng nhiều chu kỳ cấp phát và giải phóng xảy ra. Màn hình độ phân giải cao làm trầm trọng thêm vấn đề bằng cách yêu cầu cấp phát bộ nhớ lớn hơn cho các bề mặt kết xuất.
NSApp run: Một lệnh gọi API macOS khởi động vòng lặp sự kiện ứng dụng chính, xử lý tất cả các sự kiện hệ thống đang chờ trước khi trả quyền điều khiển về cho mã gọi.
Kiến trúc Threading của Emacs trên macOS:
- Main thread
- EventDispatch thread
- File descriptor handler thread
- Phụ thuộc nhiều vào các cơ chế khóa
- Xử lý vòng lặp vô hạn cho việc xử lý sự kiện
Quản lý bộ nhớ bị lỗi
Việc tạo và phá hủy nhanh chóng các tài nguyên GUI tạo ra một chuỗi các vấn đề quản lý bộ nhớ. Trong khi Emacs xử lý hiệu quả hầu hết các thao tác dọn dẹp, một số cấp phát vẫn bị bỏ sót. Các mục menu, ký tự phông chữ và bản sao trạng thái khung tích tụ trong bộ nhớ vì engine Emacs cốt lõi không mong đợi những thể hiện ảo này được tạo bởi lớp GUI.
Làm cho vấn đề tệ hơn, macOS bắt đầu lưu cache những đối tượng được cấp phát thường xuyên này, nhầm lẫn chúng với dữ liệu quan trọng. Điều này đẩy nội dung Emacs thực tế - biến, kết quả tìm kiếm và dữ liệu người dùng - ra xa hơn trong ưu tiên bộ nhớ. Cộng đồng đã phát triển các giải pháp tạm thời, nhưng kiến trúc cơ bản vẫn còn vấn đề.
Mã kiểm tra rò rỉ bộ nhớ:
(dotimes (x 10000)
(let ((frame (make-frame-command)))
(sleep-for 0.01)
(delete-frame frame)))
Đoạn mã này minh họa vấn đề rò rỉ bộ nhớ bằng cách tạo và xóa các frame một cách nhanh chóng, khiến việc sử dụng bộ nhớ tăng đều đặn.
Cộng đồng tìm kiếm các lựa chọn thay thế giữa sự thất vọng
Các vấn đề hiệu suất đã thúc đẩy nhiều người dùng Emacs lâu năm cân nhắc các lựa chọn thay thế hoặc giải pháp tạm thời. Một số đã chuyển sang chạy Emacs hoàn toàn ở chế độ terminal, hy sinh các tính năng GUI để có sự ổn định và hiệu suất. Những người khác đang khám phá các editor khác hoàn toàn, với Helix và NeoVim thường được nhắc đến như những sự thay thế tiềm năng.
Sự giật lag của Emacs trên macOS đã từ từ giết chết tôi. Đủ để tôi nghĩ đến việc hoàn toàn bỏ cuộc sau gần một thập kỷ sử dụng emacs.
Tình hình này đã tạo ra một hệ sinh thái phát triển mạnh mẽ của các bản build và fork Emacs thay thế dành riêng cho macOS, bao gồm Emacs Doom, Homebrew Emacs-Plus, và emacs-mac của Mitsuharu Yamamoto. Những lựa chọn thay thế này thường cung cấp hiệu suất tốt hơn nhưng yêu cầu người dùng duy trì các cài đặt riêng biệt.
Các phiên bản Emacs thay thế cho macOS:
- Emacs Doom
- MacPorts Emacs
- Homebrew Emacs-Plus
- Mitsuharu Yamamoto's emacs-mac
- Aquamacs
- PGTK frontend (giao diện gốc cho Wayland, có khả năng thích ứng với macOS)
Các giải pháp tiềm năng đang hiện ra
Các cuộc thảo luận đang diễn ra trong cộng đồng phát triển Emacs về các bản sửa lỗi tiềm năng, mặc dù các giải pháp sẽ không đơn giản. Việc triển khai hiện tại chỉ sử dụng ba luồng và phụ thuộc nhiều vào các cơ chế khóa. Giải quyết đúng cách sẽ yêu cầu những thay đổi kiến trúc đáng kể đối với việc xử lý sự kiện và hỗ trợ đa luồng.
Một cách tiếp cận được đề xuất bao gồm việc từ từ di chuyển mã dành riêng cho macOS sang Swift, ngôn ngữ này cung cấp quản lý bộ nhớ tốt hơn, hỗ trợ bất đồng bộ tích hợp sẵn và các tính năng an toàn luồng. Điều này có thể loại bỏ các cơ chế khóa phức tạp và cung cấp xử lý tài nguyên hiệu quả hơn, mặc dù những thay đổi như vậy sẽ đại diện cho một công việc lớn.
Cuộc điều tra làm nổi bật một thách thức rộng lớn hơn mà các ứng dụng đa nền tảng phải đối mặt: cân bằng các yêu cầu của các mô hình hệ điều hành khác nhau trong khi duy trì chức năng và hiệu suất nhất quán trên tất cả các nền tảng được hỗ trợ.
Tham khảo: Emacs: The MacOS Bug