Belajar Emacs - Performa & Tuning (Native Comp, GC)
Episode 27 of 30

Belajar Emacs - Performa & Tuning (Native Comp, GC)

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.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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.

Native Compilation: Elisp Secepat C

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:

Cek native comp di build Emacs
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:

Pastikan native comp aktif (di init.el)
(setq native-comp-speed 2)            ; level optimasi (0-3)
(setq native-comp-deferred-compilation t) ; compile background

Waspadai: 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:

Compile semua paket terpasang (sekali saja)
emacs -Q --batch --eval '(progn (package-initialize) (byte-recompile-directory (expand-file-name "~/.emacs.d/elpa") 0 t))'

Garbage Collector: Musuh Interaktivitas

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.

Tuning GC (di init.el)
(setq gc-cons-threshold (* 100 1024 1024))  ; 100 MB sebelum GC dipicu
(setq gc-cons-percentage 0.5)               ; 50% heap baru

Ada trade-off: threshold besar menunda GC, tapi memori melonjak sebelum GC terjadi. Solusi elegan: threshold besar saat idle, kecil saat mengetik:

GC responsif dengan idle timer (di init.el)
(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:

Paket gcmh (use-package)
(use-package gcmh
  :ensure t
  :config (gcmh-mode 1))

Mengukur Startup: benchmark-init

Sebelum mengoptimasi, ukur dulu. benchmark-init mencatat berapa lama tiap paket dimuat:

benchmark-init (use-package)
(use-package benchmark-init
  :ensure t
  :config
  (add-hook 'after-init-hook #'benchmark-init/deactivate)
  (benchmark-init/activate))
text
M-x benchmark-init/show-durations-tree RET   →  tree waktu muat paket
M-x benchmark-init/show-durations-tabulated RET →  tabel terurut

Yang 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:

Ukur init time dari batch
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: Senjata Lazy Loading

use-package adalah fondasi startup cepat — ia menunda loading sampai dibutuhkan. Kunci-kuncinya:

Pola use-package yang cepat (di init.el)
(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).
  • Jangan (require 'paket) di level top init — biarkan use-package yang mengelola.

Strategi Startup di Bawah 1 Detik

Urutan optimasi dari paling berdampak:

text
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 sendiri

Contoh tambahan yang kerap dipakai:

Akselerasi ringan lain (di init.el)
(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.

Pitfall Performa

  • GC threshold terlalu besar: memori bisa membengkak sebelum GC — bagi laptop dengan RAM kecil, 100 MB sudah cukup; jangan 1 GB tanpa kebutuhan.
  • :defer t tanpa :commands/:bind: paket tak akan pernah dimuat, dan kalian bingung "kenapa fiturnya tidak ada" — selalu sediakan pintu masuk loading.
  • Kompilasi native pertama: jangan restart di tengah kompilasi background; biarkan selesai sekali.
  • Mengukur dengan -Q: itu bukan ukuran config kalian — selalu ukur init asli.
  • Cache eln-cache membengkak: kadang perlu dibersihkan (rm -rf ~/.emacs.d/eln-cache) dan kompilasi ulang setelah update besar.

Penutup

Pada episode 27 ini, kalian telah mengubah Emacs dari "agak lambat" menjadi responsif.

Inti yang harus dibawa pulang:

  • Native compilation (Emacs 30) meng-compile Elisp ke kode mesin — pastikan build memakai libgccjit.
  • Tuning GC (gc-cons-threshold, gcmh) menghilangkan stutter saat mengetik.
  • benchmark-init mengukur beban startup — optimasi tanpa ukuran adalah tebakan.
  • use-package :defer/:commands/:bind adalah senjata lazy loading utama.
  • Target < 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!

Belajar Emacs - Performa & Tuning (Native Comp, GC) | Belajar Emacs