zoukankan      html  css  js  c++  java
  • Emacs 启动优化二三事

    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 "
    
    Showing package initialization statistics:
    %s"
               (mapconcat (lambda (x)
                            (format "package %s cost %.2f seconds" (car x) (cdr x)))
                          (reverse package-init-statistic)
                          "
    "
                          ))
      (message "Finished startup in %.2f seconds,  %d packages missing%s
    
    "
               (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"))))
     )
    
  • 相关阅读:
    《淘宝网》之系统质量属性分析
    《架构漫谈》读后感 之“关于软件架构师如何工作”
    《软件需求》读后感06
    《软件需求》读后感05
    Cforeach的详细用法--【转】
    事件(Event)(onclick,onchange,onload,onunload,onfocus,onblur,onselect,onmuse)【转载】
    十七、Mysql的主从(一)--主从原理
    十七、Mysql的主从(二)--主从复制部署
    十六、mysql的备份与恢复(三)--xtrabackup(XBK、Xbackup)
    十六、mysql的备份与恢复(二)--mysqldump
  • 原文地址:https://www.cnblogs.com/yangyingchao/p/3418630.html
Copyright © 2011-2022 走看看