Masalah performa yang sudah lama mengganggu pengguna Emacs di macOS akhirnya berhasil dilacak hingga ke akar penyebabnya. Bug ini menampakkan diri sebagai penggunaan memori yang meningkat secara bertahap dan akhirnya menyebabkan sistem membeku, terutama mempengaruhi pengguna dengan perangkat keras yang lebih cepat dan layar high-DPI. Setelah bertahun-tahun keluhan dan upaya debugging yang gagal, investigasi mendetail telah mengungkapkan bahwa penyebabnya terletak jauh di dalam implementasi GUI khusus macOS.
Paradoks Performa: Mac yang Lebih Cepat Membuat Emacs Lebih Lambat
Investigasi mengungkap masalah yang berlawanan dengan intuisi di mana perangkat keras Mac yang lebih kuat justru memperburuk performa Emacs. Masalah ini berasal dari cara Emacs menangani event GUI melalui pemanggilan [NSApp run]
dalam loop event utama. Setiap kali ini terjadi, sistem membuat konteks grafis yang lengkap dari awal - menginisialisasi jendela, memuat font dan glyph, serta menggambar frame - hanya untuk meruntuhkan semuanya beberapa milidetik kemudian ketika pemrosesan event selesai.
Pada mesin yang lebih cepat, siklus ini terjadi ribuan atau bahkan jutaan kali selama operasi sederhana seperti mengubah ukuran jendela. Semakin cepat perangkat keras dapat memproses event, semakin banyak siklus alokasi dan dealokasi yang terjadi. Layar high-DPI memperparah masalah dengan memerlukan alokasi memori yang lebih besar untuk surface rendering.
NSApp run: Panggilan API macOS yang memulai loop event aplikasi utama, memproses semua event sistem yang tertunda sebelum mengembalikan kontrol ke kode pemanggil.
Arsitektur Threading Emacs macOS:
- Thread utama
- Thread EventDispatch
- Thread penanganan file descriptor
- Sangat bergantung pada mekanisme penguncian
- Pemrosesan loop tak terbatas untuk penanganan event
Manajemen Memori yang Salah
Pembuatan dan penghancuran sumber daya GUI yang cepat menciptakan serangkaian masalah manajemen memori. Meskipun Emacs secara efisien menangani sebagian besar operasi pembersihan, beberapa alokasi lolos dari pengawasan. Item menu, glyph font, dan salinan state frame terakumulasi dalam memori karena mesin inti Emacs tidak mengharapkan instance hantu yang dibuat oleh layer GUI ini.
Yang memperburuk keadaan, macOS mulai menyimpan objek-objek yang sering dialokasikan ini dalam cache, salah mengira mereka sebagai data penting. Ini mendorong konten Emacs yang sebenarnya - variabel, hasil pencarian, dan data pengguna - lebih jauh dalam prioritas memori. Komunitas telah mengembangkan solusi sementara, tetapi arsitektur fundamental tetap bermasalah.
Kode Tes Kebocoran Memori:
(dotimes (x 10000)
(let ((frame (make-frame-command)))
(sleep-for 0.01)
(delete-frame frame)))
Potongan kode ini mendemonstrasikan kebocoran memori dengan cara membuat dan menghapus frame secara cepat, yang menyebabkan penggunaan memori terus meningkat.
Komunitas Mencari Alternatif di Tengah Frustrasi
Masalah performa telah mendorong banyak pengguna Emacs lama untuk mempertimbangkan alternatif atau solusi sementara. Beberapa telah beralih menjalankan Emacs secara eksklusif dalam mode terminal, mengorbankan fitur GUI demi stabilitas dan performa. Yang lain mengeksplorasi editor yang berbeda sama sekali, dengan Helix dan NeoVim sering disebutkan sebagai pengganti potensial.
Emacs jank di macOS perlahan-lahan membunuh saya. Cukup parah, sehingga saya berpikir untuk benar-benar pindah haluan setelah hampir satu dekade menggunakan emacs.
Situasi ini telah menciptakan ekosistem yang berkembang dari build dan fork Emacs alternatif khusus untuk macOS, termasuk Emacs Doom, Homebrew Emacs-Plus, dan emacs-mac milik Mitsuharu Yamamoto. Alternatif-alternatif ini sering memberikan performa yang lebih baik tetapi mengharuskan pengguna memelihara instalasi terpisah.
Build Alternatif Emacs untuk macOS:
- Emacs Doom
- MacPorts Emacs
- Homebrew Emacs-Plus
- Mitsuharu Yamamoto's emacs-mac
- Aquamacs
- PGTK frontend (native Wayland, berpotensi dapat diadaptasi ke macOS)
Solusi Potensial di Cakrawala
Diskusi sedang berlangsung dalam komunitas pengembang Emacs tentang perbaikan potensial, meskipun solusinya tidak akan sederhana. Implementasi saat ini hanya menggunakan tiga thread dan sangat bergantung pada mekanisme locking. Resolusi yang tepat akan memerlukan perubahan arsitektural yang signifikan pada penanganan event dan dukungan threading.
Satu pendekatan yang diusulkan melibatkan migrasi bertahap kode khusus macOS ke Swift, yang menawarkan manajemen memori yang lebih baik, dukungan asynchronous bawaan, dan fitur thread-safety. Ini berpotensi menghilangkan mekanisme locking yang kompleks dan menyediakan penanganan sumber daya yang lebih efisien, meskipun perubahan seperti itu akan mewakili upaya besar.
Investigasi ini menyoroti tantangan yang lebih luas yang dihadapi aplikasi lintas platform: menyeimbangkan tuntutan paradigma sistem operasi yang berbeda sambil mempertahankan fungsionalitas dan performa yang konsisten di semua platform yang didukung.
Referensi: Emacs: The MacOS Bug