歡迎光臨
每天分享高質量文章

降低 Emacs 啟動時間的高級技術 | Linux 中國

《Emacs Start Up Profiler》 的作者教你六項減少 Emacs 啟動時間的技術。

— Joe Schafer

 

簡而言之:做下麵幾個步驟:

1. 使用 Esup 進行性能檢測。
2. 調整垃圾回收的閥值。
3. 使用 use-package 來自動(延遲)加載所有東西。
4. 不要使用會引起立即加載的輔助函式。
5. 參考我的 配置[2]

從 .emacs.d 的失敗到現在

我最近宣佈了 .emacs.d 的第三次失敗,並完成了第四次 Emacs 配置的迭代。演化過程為:

1. 拷貝並粘貼 elisp 片段到 ~/.emacs 中,希望它能工作。
2. 借助 el-get 來以更結構化的方式來管理依賴關係。
3. 放棄自己從零配置,以 Spacemacs 為基礎。
4. 厭倦了 Spacemacs 的複雜性,基於 use-package 重寫配置。

本文匯聚了三次重寫和創建 《Emacs Start Up Profiler[1]》過程中的技巧。非常感謝 Spacemacs、use-package 等背後的團隊。沒有這些無私的志願者,這項任務將會困難得多。

不過守護行程樣式又如何呢

在我們開始之前,讓我反駁一下優化 Emacs 時的常見觀念:“Emacs 旨在作為守護行程來運行的,因此你只需要運行一次而已。”

這個觀點很好,只不過:

◈ 速度總是越快越好。
◈ 配置 Emacs 時,可能會有不得不通過重啟 Emacs 的情況。例如,你可能為 post-command-hook 添加了一個運行緩慢的 lambda 函式,很難刪掉它。
◈ 重啟 Emacs 能幫你驗證不同會話之間是否還能保留配置。

1、估算當前以及最佳的啟動時間

第一步是測量當前的啟動時間。最簡單的方法就是在啟動時顯示後續步驟進度的信息。

  1. ;; Use a hook so the message doesn't get clobbered by other messages.
  2. (add-hook 'emacs-startup-hook
  3. (lambda ()
  4. (message "Emacs ready in %s with %d garbage collections."
  5. (format "%.2f seconds"
  6. (float-time
  7. (time-subtract after-init-time before-init-time)))
  8. gcs-done)))

第二步、測量最佳的啟動速度,以便瞭解可能的情況。我的是 0.3 秒。

  1. # -q ignores personal Emacs files but loads the site files.
  2. emacs -q --eval='(message "%s" (emacs-init-time))'
  3. ;; For macOS users:
  4. open -n /Applications/Emacs.app --args -q --eval='(message "%s" (emacs-init-time))'

2、檢測 Emacs 啟動指標對你大有幫助

Emacs StartUp Profiler[1]》(ESUP)將會給你頂層陳述句執行的詳細指標。

圖 1: Emacs Start Up Profiler 截圖

警告:Spacemacs 用戶需要註意,ESUP 目前與 Spacemacs 的 init.el 檔案有衝突。遵照 https://github.com/jschaf/esup/issues/48 上說的進行升級。

3、調高啟動時垃圾回收的閥值

這為我節省了 0.3 秒

Emacs 預設值是 760kB,這在現代機器看來極其保守。真正的訣竅在於初始化完成後再把它降到合理的水平。這為我節省了 0.3 秒。

  1. ;; Make startup faster by reducing the frequency of garbage
  2. ;; collection. The default is 800 kilobytes. Measured in bytes.
  3. (setq gc-cons-threshold (* 50 1000 1000))
  4. ;; The rest of the init file.
  5. ;; Make gc pauses faster by decreasing the threshold.
  6. (setq gc-cons-threshold (* 2 1000 1000))

~/.emacs.d/init.el

4、不要 require 任何東西,而是使用 use-package 來自動加載

讓 Emacs 變壞的最好方法就是減少要做的事情。require 會立即加載源檔案,但是很少會出現需要在啟動階段就立即需要這些功能的。

在 use-package[4] 中你只需要宣告好需要哪個包中的哪個功能,use-package 就會幫你完成正確的事情。它看起來是這樣的:

  1. (use-package evil-lisp-state ; the Melpa package name
  2. :defer t ; autoload this package
  3. :init ; Code to run immediately.
  4. (setq evil-lisp-state-global nil)
  5. :config ; Code to run after the package is loaded.
  6. (abn/define-leader-keys "k" evil-lisp-state-map))

可以通過查看 features 變數來查看 Emacs 現在加載了那些包。想要更好看的輸出可以使用 lpkg explorer[5] 或者我在 abn-funcs-benchmark.el[6] 中的變體。輸出看起來類似這樣的:

  1. 479 features currently loaded
  2. - abn-funcs-benchmark: /Users/jschaf/.dotfiles/emacs/funcs/abn-funcs-benchmark.el
  3. - evil-surround: /Users/jschaf/.emacs.d/elpa/evil-surround-20170910.1952/evil-surround.elc
  4. - misearch: /Applications/Emacs.app/Contents/Resources/lisp/misearch.elc
  5. - multi-isearch: nil
  6. - <many more>

5、不要使用輔助函式來設置樣式

通常,Emacs 包會建議通過運行一個輔助函式來設置鍵系結。下麵是一些例子:

◈ (evil-escape-mode)
◈ (windmove-default-keybindings) ; 設置快捷鍵。
◈ (yas-global-mode 1) ; 複雜的片段配置。

可以通過 use-package 來對此進行重構以提高啟動速度。這些輔助函式只會讓你立即加載那些尚用不到的包。

下麵這個例子告訴你如何自動加載 evil-escape-mode

  1. ;; The definition of evil-escape-mode.
  2. (define-minor-mode evil-escape-mode
  3. (if evil-escape-mode
  4. (add-hook 'pre-command-hook 'evil-escape-pre-command-hook)
  5. (remove-hook 'pre-command-hook 'evil-escape-pre-command-hook)))
  6. ;; Before:
  7. (evil-escape-mode)
  8. ;; After:
  9. (use-package evil-escape
  10. :defer t
  11. ;; Only needed for functions without an autoload comment (;;;###autoload).
  12. :commands (evil-escape-pre-command-hook)
  13. ;; Adding to a hook won't load the function until we invoke it.
  14. ;; With pre-command-hook, that means the first command we run will
  15. ;; load evil-escape.
  16. :init (add-hook 'pre-command-hook 'evil-escape-pre-command-hook))

下麵來看一個關於 org-babel 的例子,這個例子更為複雜。我們通常的配置時這樣的:

  1. (org-babel-do-load-languages
  2. 'org-babel-load-languages
  3. '((shell . t)
  4. (emacs-lisp . nil)))

這不是個好的配置,因為 org-babel-do-load-languages 定義在 org.el 中,而該檔案有超過 2 萬 4 千行的代碼,需要花 0.2 秒來加載。通過查看原始碼可以看到 org-babel-do-load-languages 僅僅只是加載 ob- 包而已,像這樣:

  1. ;; From org.el in the org-babel-do-load-languages function.
  2. (require (intern (concat "ob-" lang)))

而在 ob-.el 檔案中,我們只關心其中的兩個方法 org-babel-execute: 和 org-babel-expand-body:。我們可以延時加載 org-babel 相關功能而無需呼叫 org-babel-do-load-languages,像這樣:

  1. ;; Avoid `org-babel-do-load-languages' since it does an eager require.
  2. (use-package ob-python
  3. :defer t
  4. :ensure org-plus-contrib
  5. :commands (org-babel-execute:python))
  6. (use-package ob-shell
  7. :defer t
  8. :ensure org-plus-contrib
  9. :commands
  10. (org-babel-execute:sh
  11. org-babel-expand-body:sh
  12. org-babel-execute:bash
  13. org-babel-expand-body:bash))

6、使用惰性定時器來推遲加載非立即需要的包

我推遲加載了 9 個包,這幫我節省了 0.4 秒

有些包特別有用,你希望可以很快就能使用它們,但是它們本身在 Emacs 啟動過程中又不是必須的。這些軟體包包括:

◈ recentf:儲存最近的編輯過的那些檔案。
◈ saveplace:儲存訪問過檔案的光標位置。
◈ server:開啟 Emacs 守護行程。
◈ autorevert:自動多載被修改過的檔案。
◈ paren:高亮匹配的括號。
◈ projectile:專案管理工具。
◈ whitespace:高亮行尾的空格。

不要 require 這些軟體包,而是等到空閑 N 秒後再加載它們。我在 1 秒後加載那些比較重要的包,在 2 秒後加載其他所有的包。

  1. (use-package recentf
  2. ;; Loads after 1 second of idle time.
  3. :defer 1)
  4. (use-package uniquify
  5. ;; Less important than recentf.
  6. :defer 2)

不值得的優化

不要費力把你的 Emacs 配置檔案編譯成位元組碼了。這隻節省了大約 0.05 秒。把配置檔案編譯成位元組碼還可能導致源檔案與編譯後的檔案不一致從而難以重現錯誤進行除錯。


via: https://blog.d46.us/advanced-emacs-startup/

作者:Joe Schafer[8] 選題:lujun9972 譯者:lujun9972 校對:wxy

赞(0)

分享創造快樂