Emacs 啟動優化二三事


這兩天一直在折騰 Emacs 的配置文件以優化啟動時間,這里做個簡單的總結。

1 啟動(加載)時間檢測

檢測配置文件的加載時間非常簡單,開始加載之間加個時間戳,加載結束之后計算一下時間即可:

 ;; Load all configuration and packages.
(let ((ts-init (current-time)))
  (setq missing-packages-list nil
        package-init-statistic nil)
  (try-require '01-rc-generic t)
  (try-require '02-rc-functions t)
  (try-require '04-rc-other-modes t)
  (try-require '05-rc-misc t)
  (try-require '03-rc-prog-mode t)
  (try-require '06-rc-complete t)
  (try-require '09-rc-keybindings t)
  (try-require '10-emacs-custome t)
  (try-require '99-proj t)
  (try-require '100-private t)
  ;; Report package statistics.

  (message "\n\nShowing package initialization statistics:\n%s"
           (mapconcat (lambda (x)
                        (format "package %s cost %.2f seconds" (car x) (cdr x)))
                      (reverse package-init-statistic)
                      "\n"
                      ))
  (message "Finished startup in %.2f seconds,  %d packages missing%s\n\n"
           (float-time (time-since ts-init)) (length missing-packages-list)
           (if missing-packages-list
               ". Refer to `missing-packages-list` for missing packages."
             ".")))

上面代碼中有個 try-require ,最早取自 這里 , 后來對他加了一些改動:

 ;; Function to collect information of packages.
(defvar missing-packages-list nil
  "List of packages that `try-require' can't find.")

(defvar package-init-statistic nil "Package loading statistics")

;; attempt to load a feature/library, failing silently
(defun try-require (feature &optional click)
  "Attempt to load a library or module. Return true if the
library given as argument is successfully loaded. If not, instead
of an error, just add the package to a list of missing packages."
  (condition-case err
      ;; protected form
      (let ((timestamp (current-time))
            (package (if (stringp feature) feature (symbol-name feature))))
        (if (stringp feature)
            (load-library feature)
          (require feature))
        (if click
            (add-to-list 'package-init-statistic
                         (cons (if (stringp feature) feature (symbol-name feature))
                               (float-time (time-since timestamp)))))
        (message "Checking for library `%s'... Found, cost %.2f seconds"
                 feature (float-time (time-since timestamp))))
    ;; error handler
    (file-error  ; condition
     (progn
       (message "Checking for library `%s'... Missing" feature)
       (add-to-list 'missing-packages-list feature 'append))
     nil)))

該函數檢查 feature 時候存在,如果在則加載並輸出加載所用的時間,如果不在,輸出警告信息並記錄缺失的文件。 同時,如果可選參數 click 被設置成,則還將加載耗費的時間信息記錄到全局的 package-init-statistic 中,以便后續查詢。

在我的機器上,上述配置會產生下面的信息:

Showing package initialization statistics:
package 01-rc-generic cost 0.16 seconds
package 02-rc-functions cost 0.15 seconds
package 04-rc-other-modes cost 0.03 seconds
package 05-rc-misc cost 0.53 seconds
package 03-rc-prog-mode cost 1.22 seconds
package 06-rc-complete cost 0.18 seconds
package 09-rc-keybindings cost 0.08 seconds
package 10-emacs-custome cost 0.00 seconds
package 99-proj cost 0.00 seconds
package 100-private cost 0.01 seconds
Finished startup in 2.40 seconds,  0 packages missing.

可見配置信息加載之后總共話費了 2.4 秒,相對於 Emacs 強大的功能來說,加載時間還算不錯,尤其是啟動之后 server-mdoe 會自動啟動,此時再用 emacs-client 來打開文件時,時間消耗會非常小了。

2 autoload and eval-after-load

上述代碼中僅簡單說明了要通過 try-require 來加載哪些配置文件,單個配置文件中則大量使用了 autoloadeval-after-load 來將 mode 相關的配置時間盡量延后,關於 autoloadeval-after-load這里 有比較詳細的解釋,簡單來說,

  • autoload 可以從讓 Emacs 知道去哪個文件中去找指定的函數或者定義,但不用去真正的加載它。
  • eval-after-load 則可以安排 Emacs 在真正加載某個文件或者符號的時候去做一些事情。

有時候我還想知道 eval-after-load 安排的事情在執行時候花費多少時間,為此寫了個 Emacs Macro:

(defmacro yc/eval-after-load (name &rest args)
  "Macro to set expressions in `arg` to be executed after `name` is loaded."
  `(eval-after-load ,name
     ',(append (list 'progn
                     `(let ((ts (current-time)))
                        (message "Loading configuration for: %s..." ,name)
                        ,@args
                        (message "Configuration for %s finished in %.2f seconds" ,name
                                 (float-time (time-since ts ))))))))

使用實例:

(autoload 'erlang-mode "erlang" "erlang"  t)
(add-to-list 'auto-mode-alist '("\\.erl\\'" . erlang-mode))
(add-to-list 'auto-mode-alist '("\\.escript\\'" . erlang-mode))
(yc/eval-after-load "erlang"
                    (try-require 'erlang-flymake)
                    (let ((erl-root
                           (cond
                            ((string= system-type "windows-nt") nil)
                            ((string= system-type "darwin") nil)
                            (t "/usr/lib/erlang"))))
                      (setq erlang-root-dir erl-root)))

上面的代碼告訴 Emacs 從 "erlang.el" 這個文件中可以找到 erlang-mode 這個 Mode, 並安排在加載 erlang.el 之后加載 erlang-flymake 並設置一些變量。 當打開一個 erl 文件后,會有如下的輸出:

Loading configuration for: erlang...
Checking for library `erlang-flymake'... Found, cost 0.00 seconds
Configuration for erlang finished in 0.00 seconds

3 backtrace

backtrace 當然是用來打印 backtrace 的,它不會幫助解決文件加載速度,但如果我們覺得某些文件不應該加載,但啟動時候卻自動加載了,那么可以在 eval-after-load 的后面加上這句話,然后看下 backtrace

我遇到的一個問題是,每次 Emacs 啟動時候都會自動嘗自動加載 magit 的配置,搜索無果后在 magit 的配置里面加上 backtrace , 結果定位到是其他的 package 里用了 (require 'magit) ,對那個 package 稍作修改即可。

4 function and advice

這個 advice 其實和 Emacs 啟動加載沒什么關系,但懶得單獨再寫什么東西了,這里簡單記上兩筆。

  • Emacs 很獨特,用戶可以為某個 function 給出一些建議,並可以讓該建議在函數執行之前,之后,或者之前加上之后來執行。
  • 對於上面的第三種情況 (around-advice),如果 advice 中沒有用到 ad-do-it ,則 function 本身無任何作用,相當於覆蓋了原有的function。
  • advice 中可以通過變量 ad-return-value 來設置返回值。

更具體的信息可以查看 elisp 的 info ,這里幾下一個簡單的例子, hideif 中的 hide-ifdefs 總會輸出信息來通知用戶 hiding 的啟動和結束,很討厭,我們可以通過 advice 來直接覆蓋掉原來的函數:

(autoload 'hide-ifdef-mode "hideif" ""  t)

(yc/eval-after-load
 "hideif"
 (setq hide-ifdef-shadow t)
 (setq-default hide-ifdef-env
               '((__KERNEL__ . 1)
                 (DEBUG . 1)
                 (CONFIG_PCI . 1)
                 ))

 (defadvice hide-ifdefs (around yc/hideifdefs (&optional nomsg))
   (interactive)
   (setq hif-outside-read-only buffer-read-only)
   (unless hide-ifdef-mode (hide-ifdef-mode 1)) ; turn on hide-ifdef-mode
   (if hide-ifdef-hiding
       (show-ifdefs))           ; Otherwise, deep confusion.
   (setq hide-ifdef-hiding t)
   (hide-ifdef-guts)
   (setq buffer-read-only (or hide-ifdef-read-only hif-outside-read-only))
   )
 (ad-activate 'hide-ifdefs)


 (defun yc/add-to-ifdef-env (lst)
   "Helper function to update ifdef-env."
   (let (kvp k v)
     (while (setq kvp (pop lst))
       (setq k (car kvp)
             v (cdr kvp))
       (hif-set-var k v)
       (when (and (symbolp k) (symbolp v))
         (add-to-list 'semantic-lex-c-preprocessor-symbol-map (cons (symbol-name k)
                                                                    (symbol-name v)))))))

 (defun yc/toggle-hide-if-def-shadow ()
   "Toggle shadow"
   (interactive)
   (setq hide-ifdef-shadow (not hide-ifdef-shadow))
   (hide-ifdefs))

 ;; Copied from Ahei:
 ;; http://code.google.com/p/dea/source/browse/trunk/my-lisps/hide-ifdef-settings.el
 (defun hif-goto-endif ()
   "Goto #endif."
   (interactive)
   (unless (or (hif-looking-at-endif)
               (save-excursion)
               (hif-ifdef-to-endif))))

 (defun hif-goto-if ()
   "Goto #if."
   (interactive)
   (hif-endif-to-ifdef))

 (defun hif-goto-else ()
   "Goto #else."
   (interactive)
   (hif-find-next-relevant)
   (cond ((hif-looking-at-else)
          'done)
         ((hif-ifdef-to-endif) ; find endif of nested if
          (hif-goto-endif)) ; find outer endif or else

         ((hif-looking-at-else)
          (hif-goto-endif)) ; find endif following else

         ((hif-looking-at-endif)
          'done)

         (t
          (error "Mismatched #ifdef #endif pair"))))
 )


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM