5 poin oleh GN⁺ 4 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Kecepatan terminal yang digunakan sepanjang hari sangat menentukan efisiensi kerja, dan jeda kecil saat membuka tab baru, mengetik, atau autocompletion menjadi tidak efisien ketika terakumulasi ratusan kali dalam sehari
  • Shell interaktif yang termuat sepenuhnya dapat dijalankan hanya dalam sekitar 30 milidetik meski mencakup autocompletion, syntax highlighting, autosuggestion, fzf, dan direnv, serta tab baru kini terbuka seketika
  • Rahasia terbesarnya adalah tidak memakai framework dan plugin manager seperti oh-my-zsh atau prezto; cukup git clone langsung 3 plugin lalu source dari .zshrc
  • Dengan caching compinit, lazy-loading, prompt asinkron, terminal berakselerasi GPU, dan lainnya, latensi saat startup, prompt, dan input semuanya diminimalkan
  • Sebagian besar optimasi bukan soal menambahkan sesuatu, melainkan menyingkirkan hal yang tidak perlu, dan kuncinya adalah disiplin untuk hanya menambahkan yang benar-benar sering dipakai

Mengapa terminal cepat itu penting

  • Hampir semua pekerjaan dilakukan di dalam terminal, menggunakan Git, kubectl, tmux, koneksi ssh ke server, dan lain-lain sepanjang hari
  • Karena begitu sering dipakai, alat ini harus cepat; jeda saat membuka tab baru, mengetik karakter, atau tab completion terasa ratusan kali setiap hari
  • Akumulasi jeda kecil seperti ini ibarat death by a thousand cuts

Hasil pengukuran kecepatan startup shell

  • Setelah berbagai pembaruan, shell kini mulai dalam sekitar 30 milidetik, diukur dengan perintah for i in {1..5}; do /usr/bin/time zsh -i -c exit; done
  • Shell interaktif lengkap dengan autocompletion, syntax highlighting, autosuggestion, fzf, dan direnv semuanya termuat dalam waktu yang lebih singkat dari satu frame tunggal pada 30fps
  • Ini bukan hasil dari satu proyek optimasi besar, melainkan buah dari kebiasaan bertahun-tahun menjaga shell tetap minimal dan cepat
  • Semua konfigurasinya dipublikasikan di repositori dotfiles

Tanpa framework

  • Keuntungan terbesar justru datang dari hal yang tidak ada: tidak memakai oh-my-zsh, prezto, maupun plugin manager
  • Jika hanya memakai sekitar 5% dari ratusan plugin dan tema milik oh-my-zsh, maka setiap kali membuka shell kita tetap membayar biaya waktu dan sumber daya komputasi untuk 95% sisanya
  • Plugin manager menambah overhead lagi di atas itu
  • Yang dipakai tepat 3 plugin saja, dan skrip instalasi melakukan git clone satu kali lalu source dari .zshrc
    • fzf-tab, zsh-autosuggestions, zsh-syntax-highlighting
    • Tidak ada plugin manager yang melakukan resolusi dependensi saat startup, dan source file yang sudah ada di disk pada praktiknya hampir tidak berbiaya

Caching autocompletion

  • compinit adalah salah satu pekerjaan termahal dalam .zshrc biasa, karena secara default ia melakukan audit keamanan terhadap semua file autocompletion setiap kali shell dibuka
  • Solusinya adalah menjalankan proses penuh hanya jika cache (.zcompdump) lebih tua dari 24 jam, dan selain itu melewati pemeriksaan dengan -C
    • Penentu glob #qNmh-24 berarti "ada dan dimodifikasi dalam 24 jam terakhir"
    • Jadi compinit penuh hanya dijalankan sekali sehari, dan selebihnya menggunakan pembacaan cache
    Iklan

Lazy-loading

  • nvm adalah salah satu penyebab startup shell lambat yang paling terkenal; jika langsung di-source saat startup, ia bisa dengan mudah menambah 0,5 detik
  • nvm tidak dibutuhkan di semua shell, melainkan hanya saat mengetik nvm, jadi ia dibungkus dalam fungsi yang menggantikan dirinya sendiri saat pertama kali dipakai
    • Panggilan nvm pertama menghapus stub, me-source nvm yang sebenarnya (dengan --no-use agar tidak sekaligus menyelesaikan versi node), lalu meneruskan argumennya
  • Autocompletion kubectl juga memakai pendekatan yang sama; karena ia memanggil biner kubectl untuk menghasilkan skrip completion, maka ia hanya dimuat setelah benar-benar dijalankan pertama kali
  • Semua tool yang menyuruh menaruh eval "$(tool init zsh)" di .zshrc akan melakukan fork proses saat startup lalu mengevaluasi outputnya, jadi itu adalah kandidat lazy-loading
  • direnv dan fzf cepat dan sering dipakai, jadi tetap dimuat langsung; yang penting adalah menilai dengan ketat apa yang benar-benar sering digunakan

Prompt non-blocking

  • Prompt yang menjalankan git status secara sinkron akan melambat di repositori yang agak besar, dan ini terasa setiap kali menekan Enter sehingga bisa lebih buruk daripada startup yang lambat
  • Digunakan pure, yang merender prompt segera lalu mengisi informasi git secara asinkron ketika sudah siap
  • Pernah sempat mencoba menggantinya dengan vcs_info bawaan zsh, tetapi perilaku asinkron pure terasa lebih baik
  • Git status asinkron juga bisa diimplementasikan sendiri di prompt, tetapi pure sudah membungkus kebutuhan itu dengan baik

Emulator terminal itu sendiri

  • Startup shell hanyalah setengah dari cerita; emulator itu sendiri juga menambah latensi input
  • Digunakan Ghostty, terminal native berakselerasi GPU, dengan konfigurasi hanya 7 baris
  • Dikombinasikan dengan alias tmux new -A -s main (t), jendela terminal baru langsung mengembalikan ke sesi yang sudah ada
Iklan

Cara mengukur performa shell sendiri

  • Kita bisa langsung mengukur ke mana waktu di terminal terpakai, dan ada tiga jenis jeda yang perlu dicek: waktu startup, latensi prompt, dan latensi input
  • Pengukuran dasar dilakukan dengan menjalankan time zsh -i -c exit beberapa kali; eksekusi pertama selalu lebih lambat karena cold cache
    • Di bawah 100ms sudah oke, di bawah 50ms luar biasa, dan di atas 500ms berarti ada yang perlu dibenahi
  • Untuk statistik yang akurat, gunakan hyperfine: hyperfine --warmup 3 'zsh -i -c exit'
  • Manfaatkan profiler bawaan zsh
    • Tambahkan zmodload zsh/zprof di bagian paling atas .zshrc dan zprof di bagian paling bawah untuk menampilkan tabel terurut tentang ke mana waktu habis digunakan
    • Item teratas biasanya compinit, source untuk nvm.sh, dan eval "$(...)"; perbaiki mulai dari yang paling atas lalu ulangi pengujian
    • Setelah selesai, hapus dua baris itu
  • Jika zprof belum cukup, lacak seluruh startup dengan timestamp: zsh -ixc exit 2>&1 | ts -i '%.s' | sort -rn | head -20
    • Atau set PS4='+%D{%s.%6.}: ' lalu jalankan zsh -ixc exit 2> startup.log, kemudian cari loncatan besar antarbaris
  • Startup bisa cepat tetapi redraw prompt tetap lambat; pindah ke repositori git terbesar dengan cd, lalu tekan Enter. Jika ada jeda sebelum prompt berikutnya muncul, berarti prompt melakukan pekerjaan sinkron
    • Opsinya adalah beralih ke prompt asinkron atau menghapus fitur Git

Penutup

  • Sebagian besar optimasi adalah soal membuang hal-hal yang tidak perlu, dan kuncinya adalah bertindak dengan sengaja serta hanya menambahkan apa yang benar-benar akan dipakai
  • Dengan begitu, puluhan sesi yang dibuka setiap hari semuanya terbuka seketika, dan terminal terasa bukan sebagai aplikasi yang harus ditunggu, melainkan alat yang seperti perpanjangan pikiran
  • Untuk alat yang dipakai sepanjang hari, kecepatan seperti ini tidak bisa ditawar
  • Semua konfigurasi di atas dipublikasikan di repositori dotfiles

1 komentar

 
GN⁺ 4 jam lalu
Komentar Lobste.rs
  • Secara teknis, yang dimaksud kebanyakan bukan terminal, melainkan shell

  • Lebih baik memakai alat dengan default yang benar, jadi pakai saja fish

    • ZSH kantor saya sejak sekitar setahun lalu menjadi sangat lambat tanpa alasan masuk akal, jadi saya mencoba fish, dan saya sangat suka fitur-fitur peningkat kualitas hidupnya
      Saya suka karena fitur seperti tab completion modern yang bisa dipilih dengan tombol panah sudah tersedia secara bawaan, dan di perangkat pribadi saya masih memakai ZSH, tapi itu karena saya belum sempat mengutak-atik pengaturan Nix dan home manager
    • Akan bagus kalau ada yang membuat fish yang kompatibel dengan bash
      Shell dengan default yang masuk akal dan completion bawaan yang cepat, tanpa harus membuang atau menulis ulang alat-alat berbasis bash
    • Hidup terlalu singkat untuk memasang alat baru, cukup punya default yang masuk akal saja
  • Kadang saya bertanya-tanya apakah hal-hal seperti prompt non-blocking atau terminal berbasis OpenGL benar-benar sepadan dibanding sekadar memakai PS1="\W: " di xterm

    • Selama beberapa tahun saya sengaja tidak memakai xterm, lalu setelah melihat-lihat berbagai emulator terminal, saya cukup terkejut karena xterm mendukung font OpenType, UTF-8, sebagian besar emoji, warna 24-bit, dan penggunaan memori yang rendah
      Ditambah lagi sangat cepat dan punya keunggulan sebagai “standar”, jadi bug yang tersisa pun umumnya kecil atau program yang berjalan di dalamnya kemungkinan akan menganggap perilakunya normal
      Jadi saya kembali memakai xterm
    • Tidak sepadan
      Startup zsh pada dasarnya sangat cepat, dan hanya melambat kalau pengguna sendiri yang membuatnya lambat
      Cukup jangan memasukkan banyak hal yang tidak dipahami, termasuk library yang menyebut dirinya “minimal” tetapi mengeksekusi ratusan perintah setiap kali membangun prompt
      Konfigurasi zsh saya adalah beberapa ratus baris yang berkembang sangat pelan sejak era 90-an, dan saya memahami setiap barisnya serta tahu kenapa itu ada
      Saya tidak pernah secara khusus mencoba membuatnya cepat, tapi tetap mulai dalam 20ms, dan kalau saya membuat perubahan bodoh yang bisa memperlambatnya, saya langsung sadar dan bisa memperbaikinya
  • Saya tidak suka benchmark rusak seperti time zsh -i -c exit yang masih sering dipakai
    Itu mengukur hal yang sepenuhnya salah, dan beberapa pengelola plugin zsh sampai mengorbankan latensi startup shell yang nyata demi mengoptimalkan metrik tak berguna ini
    zsh-bench punya bagian yang menjelaskan kenapa benchmark ini tidak bermakna: https://github.com/romkatv/zsh-bench#how-not-to-benchmark
    Metrik seperti first prompt latency atau input latency yang diukur zsh-bench jauh lebih berguna

  • Saya senang ternyata ini bukan soal bug terminal dengan akselerasi GPU
    Caching completion adalah tip yang bagus, dan saya memakai zsh di Mac kantor, sampai-sampai baru terpikir membuka tab baru saja beachball sudah muncul, jadi semoga ini membantu
    Untuk completion kubectl, saya penasaran bagian lambatnya ada pada pembuatan completion atau saat memuatnya, dan kalau yang lambat adalah yang pertama, apakah menyimpannya ke file lalu memuat dari sana akan mengurangi waktu startup
    Di jj mereka melakukan itu, dan sejak pindah ke jj saya juga membuang prompt yang menjalankan git status
    Sayang penulis tidak menampilkan waktunya sendiri, jadi saya tidak bisa tahu apakah 0,287 detik saya itu rata-rata atau lambat
    Setelah saya ukur, .bashrc yang hampir kosong memakan 0,007 detik, setelah key binding skim jadi 0,043 detik, setelah mise jadi 0,115 detik, setelah completion jj jadi 0,186 detik, dan jika sampai membaca /etc/bashrc jadi 0,294 detik, jadi tampaknya masih ada ruang untuk perbaikan

    • Di artikel itu shell sendiri disebut 30ms di awal, dan pada pengujian time shell -c exit yang sama, milik saya sekitar 50ms
      Hal paling menjengkelkan saat memakai lingkungan Linux orang lain adalah animasi tidak berguna yang ada di mana-mana
      Di komputer saya, saat menekan shortcut, jendela terminal terbuka nyaris seketika, kadang hanya terlihat kedipan singkat antara jendela dan prompt
      Karena itu yang penting adalah pengujian end-to-end penuh: buka jendela baru, lakukan sesuatu di shell, lalu tutup, dan time myterm lalu menekan Ctrl+D di jendela untuk menutupnya selalu di bawah 0,120 detik
      Jika animasi dan compositing yang tidak perlu dihilangkan, banyak hal jadi mungkin, dan saat melihat perbedaan dua spreadsheet saya juga memaksimalkan dua jendela lalu berganti cepat dengan shortcut roll-up jendela, sehingga perbedaannya langsung terlihat
      Melakukan hal yang sama di Windows dengan animasi Excel terlalu mengganggu
    • Di bawah 100ms tampaknya sulit di lingkungan saya
      Bahkan dengan konfigurasi kosong, zsh -i -c exit rata-ratanya 129,8ms, dan konfigurasi penuh memakan sekitar 250ms, jadi kurang lebih sama
      Dengan caching compinit saya memang memangkas rata-rata sekitar 5ms, tapi karena completion bisa saja hilang, menurut saya usahanya tidak terlalu sepadan
  • Belakangan startup zsh terasa melambat hampir seperti macet total, dan meskipun saya belum menemukan penyebab pastinya, saya sudah memastikan bahwa compinit mengambil sebagian besar jalur kritis
    Saya mengimplementasikan caching hampir sama seperti yang disarankan artikel itu dan berhasil menghilangkan kelambatan tersebut, dan setelah melihat glob qualifier yang keren itu saya merasa cara saya juga harus diperbaiki
    Saya bahkan tidak tahu fitur seperti itu memungkinkan, dan sejujurnya terlihat agak mencurigakan, tapi tetap akan saya pakai
    Sebelumnya saya memakai cara yang agak kasar dengan date -Id saat membuat path target
    Saya suka alat yang dikonfigurasi dengan bahasa pemrograman yang benar-benar lengkap seperti zsh, sehingga kita bisa mengimplementasikan sendiri tanpa perlu penulisnya menambahkan fitur caching
    Selama hampir 20 tahun memakai zsh saya tidak pernah memakai framework atau plugin manager, dan tampaknya hal-hal seperti itu lebih sering dipakai untuk styling
    Saya beruntung karena tidak peduli dengan estetika lingkungan komputasi saya, dan prompt buatan saya sendiri juga sederhana, kecil, informatif, tapi sama sekali tidak mewah, serta saya memakai tema terminal default berlatar hitam

    • Caching compinit membuat frustrasi karena cache bisa menjadi usang
      Beberapa instance shell bisa melakukan hal yang sama secara paralel, dan saya sering mengalaminya saat menjalankan instance paralel untuk latihan di tmux
      Selain itu, home directory juga bisa dibagi di antara beberapa host, terutama container, jadi akhirnya saya menatanya dengan pendekatan yang mencakup lock file, pemeriksaan kedaluwarsa, dan penanganan kondisi zcompile
    • Waktu muat ZSH memburuk sekali sampai saya akhirnya mencoba fish
      Sayangnya, konfigurasi fish juga tampaknya perlahan bergerak ke arah yang sama, dan saat waktu senggang hari Senin saya berencana melakukan profiling untuk melihat apakah teknik lazy loading benar-benar berguna untuk kasus saya
      Sebagian besar waktu lambatnya kemungkinan berasal dari modul git milik Starship, tapi ada juga cukup banyak alias dan fungsi helper yang bisa di-lazy-load
  • Di Emacs, sejak lama sudah ada shell staging di background yang diinisialisasi lebih dulu
    Membuka terminal berarti membuka jendela baru ke buffer itu lalu mengganti namanya, dan mem-fork thread yang menyiapkan shell lagi untuk penggunaan berikutnya
    Jadi tidak ada latensi startup
    Saya ingat dulu pernah mencoba memaksa solusi di luar Emacs dengan reptyr, tapi akhirnya tidak terus memakainya, dan saya juga tidak terlalu ingat alasannya
    https://github.com/nelhage/reptyr

    • Rasanya seperti proses zygote di Android, saya suka
  • Saat menyelidiki hal serupa, saya menemukan bahwa zsh-abbr memakan sekitar 100ms waktu startup, tapi saya masih oke dengan itu
    Saya mungkin bisa memangkas 10ms di sana-sini, tapi kalau melihat fitur yang hilang, rasanya tidak sepadan
    Saya akan hidup dengan waktu startup sekitar 300ms; itu cukup cepat, dan saya juga jarang membuka terminal bertubi-tubi atau harus langsung mengetik saat itu juga
    Meski begitu, artikelnya bagus, saya jadi tahu hyperfine, dan jadi melihat-lihat beberapa file startup zsh

  • Berkat ini saya akhirnya memperbaiki zshrc yang sudah lama tertunda, dan sekarang turun sampai 80ms, sangat mantap

  • Hidup saya cukup panjang untuk menoleransi terminal yang lambat, dan kadang saya malah berharap terminal lebih lambat
    Misalnya, kalau konsol root punya jeda default 5 detik sebelum benar-benar mengeksekusi, agar ada waktu membatalkan typo dengan Ctrl+C, mungkin saya bisa menghemat beberapa hari di masa muda saya yang dulu suka nekat