Memburu milidetik di Emacs: native compilation (libgccjit) untuk eksekusi Elisp super cepat, tuning garbage collector, benchmark-init untuk mengukur beban startup, dan strategi lazy loading use-package agar Emacs menyala di bawah satu detik.

Kalian sudah punya daemon (episode 24), keybinding nyaman (episode 25), dan otomasi Elisp (episode 26). Episode 27 menangani keluhan terakhir: kecepatan. Emacs modern dengan native compilation + tuning yang tepat menyala di bawah 1 detik dan mengeksekusi perintah tanpa rasa "loading".
Mengapa penting? Karena performa adalah pengalaman. Setup yang lambat membuat kalian menunda membuka Emacs, dan di dunia kerja hal itu berarti friction di setiap langkah. Lebih dari itu: memahami di mana waktu habis (GC, eager loading, native compile) mengajarkan cara berpikir optimasi yang berlaku di mana pun.
Sejak Emacs 30, native compilation berbasis libgccjit adalah default: Elisp di-compile ke kode mesin, bukan hanya bytecode. Hasilnya fungsi seperti replace-regexp, rendering org-mode, dan motion paket terasa jauh lebih responsif.
Verifikasi instalasi:
emacs --version
# cek dukungan libgccjit
emacs -Q --batch --eval '(princ (if (and (fboundp (quote native-comp-available-p)) (native-comp-available-p)) "native-comp OK" "tidak ada"))'Kalau Emacs kalian di-build tanpa libgccjit, sebaiknya ganti ke build dengan native comp (distro seperti Debian/Ubuntu menyediakan binary resmi GNU atau paket emacs-nativecomp). Verifikasi semua Elisp ter-compile:
(setq native-comp-speed 2) ; level optimasi (0-3)
(setq native-comp-deferred-compilation t) ; compile backgroundWaspadai: kompilasi pertama setelah update paket berjalan di background dan menulis ke ~/.emacs.d/eln-cache/ — jangan panik kalau sesi pertama terasa hangat. Kompilasi massal sekali saja:
emacs -Q --batch --eval '(progn (package-initialize) (byte-recompile-directory (expand-file-name "~/.emacs.d/elpa") 0 t))'Elisp memakai garbage collector untuk membebaskan memori. Default Emacs memicu GC sangat sering (gc-cons-threshold kecil) sehingga ada pause halus saat mengetik — yang terasa sebagai stutter.
(setq gc-cons-threshold (* 100 1024 1024)) ; 100 MB sebelum GC dipicu
(setq gc-cons-percentage 0.5) ; 50% heap baruAda trade-off: threshold besar menunda GC, tapi memori melonjak sebelum GC terjadi. Solusi elegan: threshold besar saat idle, kecil saat mengetik:
(defun my/do-gc-on-idle ()
"GC saat idle agar tidak mengganggu mengetik."
(setq gc-cons-threshold (* 100 1024 1024))
(setq gc-cons-percentage 0.5))
(defun my/do-gc-on-typing ()
"Kembalikan threshold kecil saat mengetik."
(setq gc-cons-threshold (* 5 1024 1024))
(setq gc-cons-percentage 0.1))
(add-hook 'focus-out-hook #'my/do-gc-on-idle)
(add-hook 'focus-in-hook #'my/do-gc-on-typing)
(run-with-idle-timer 5 t #'garbage-collect)Alternatif siap pakai: paket gcmh (Garbage Collector Magic Hack) yang mengotomasi pola di atas:
(use-package gcmh
:ensure t
:config (gcmh-mode 1))Sebelum mengoptimasi, ukur dulu. benchmark-init mencatat berapa lama tiap paket dimuat:
(use-package benchmark-init
:ensure t
:config
(add-hook 'after-init-hook #'benchmark-init/deactivate)
(benchmark-init/activate))M-x benchmark-init/show-durations-tree RET → tree waktu muat paket
M-x benchmark-init/show-durations-tabulated RET → tabel terurutYang tampak biasanya mengejutkan: paket yang kecil tapi di-require di awal menyumbang detik, sementara paket besar yang di-load malas tidak terasa.
Ukur startup dari shell juga:
emacs -Q --batch --eval '(princ (format "init: %.2fs" (emacs-init-time)))'
# dengan init penuh:
emacs --batch --eval '(princ (format "init penuh: %.2fs" (emacs-init-time)))'Important
Target "< 1 detik" harus diukur pada kondisi nyata: dengan init asli kalian, bukan -Q. -Q selalu cepat karena tidak memuat apa pun — ia hanya baseline, bukan ukuran kerja nyata.
use-package adalah fondasi startup cepat — ia menunda loading sampai dibutuhkan. Kunci-kuncinya:
(use-package magit
:ensure t
:defer t ; jangan load saat start
:bind (("C-c m g" . magit-status))
:commands (magit-status magit-log)) ; autoload saat perintah dipanggil
(use-package vertico
:ensure t
:init (vertico-mode 1) ; mode ringan: aktif segera
:defer 1) ; tapi ditunda 1 detik
(use-package yasnippet
:ensure t
:defer 1
:hook (prog-mode . yas-minor-mode))Panduan:
:defer t + :commands: paket hanya dimuat saat perintahnya dipanggil.:bind membungkus autoload: menekan tombol langsung memuat paket.:hook memuat paket saat mode aktif — tanpa load di startup.:after memuat setelah paket lain selesai (hindari dependency berantai).(require 'paket) di level top init — biarkan use-package yang mengelola.Urutan optimasi dari paling berdampak:
1. Hapus (require ...) global → ganti :defer/:commands/:bind
2. Nonaktifkan paket yang tak pernah dipakai
3. Tidurkan mode berat: org-mode di-load via :defer (bind "C-c o")
4. Kurangi (setq ...) besar di startup → pindah ke :config paket
5. byte/native-compile init.el sendiriContoh tambahan yang kerap dipakai:
(require 'cl-lib)
(setq comp-deferred-compilation t)
(if (native-comp-available-p)
(setq native-comp-async-jobs-number 2))Hindari hack yang menyelamatkan milidetik tapi merusak fungsi (seperti menonaktifkan file-name-handler-alist tanpa memahami konsekuensinya) — gunakan kebiasaan :defer yang sudah teruji.
:defer t tanpa :commands/:bind: paket tak akan pernah dimuat, dan kalian bingung "kenapa fiturnya tidak ada" — selalu sediakan pintu masuk loading.-Q: itu bukan ukuran config kalian — selalu ukur init asli.eln-cache membengkak: kadang perlu dibersihkan (rm -rf ~/.emacs.d/eln-cache) dan kompilasi ulang setelah update besar.Pada episode 27 ini, kalian telah mengubah Emacs dari "agak lambat" menjadi responsif.
Inti yang harus dibawa pulang:
gc-cons-threshold, gcmh) menghilangkan stutter saat mengetik.:defer/:commands/:bind adalah senjata lazy loading utama.< 1 detik dicapai dari kebiasaan deferred, bukan hack.Di episode 28 selanjutnya, kita melihat peta besar: ekosistem & tren modern 2026 — Emacs 30.2/31, Eglot 1.20-1.21, paket kunci, dan arah perkembangan "batteries included". Sampai jumpa di episode 28!