用 doom-emacs 的增量加载来提高 emacs 流畅性

最近在重构自己的配置,在速度优化方面主要参考的是 doom-emacs 。在浏览了其源码中相关的配置后,感觉受益匪浅,印象尤其深刻的是它的增量加载:诸如 org、magit之类的包一下子加载可能会有卡顿,因此可以在 emacs 空闲的时候先部分加载其依赖或组件,从而提高整体的流畅程度。

我重构自己配置的原因之一就是平时进入 org-mode 之类的主模式的时候免不了要卡顿一下,这下真的是刚想打瞌睡就有人送来枕头。我把这部分的代码从 doom 里抄了出来,和大家分享一下

(defvar elemacs-incremental-packages '(t)
  "A list of packages to load incrementally after startup. Any large packages
  here may cause noticeable pauses, so it's recommended you break them up into
  sub-packages. For example, `org' is comprised of many packages, and can be
  broken up into:

    (elemacs-load-packages-incrementally
     '(calendar find-func format-spec org-macs org-compat
       org-faces org-entities org-list org-pcomplete org-src
       org-footnote org-macro ob org org-clock org-agenda
       org-capture))

  This is already done by the lang/org module, however.

  If you want to disable incremental loading altogether, either remove
  `doom-load-packages-incrementally-h' from `emacs-startup-hook' or set
  `doom-incremental-first-idle-timer' to nil. Incremental loading does not occur
  in daemon sessions (they are loaded immediately at startup).")

(defvar elemacs-incremental-first-idle-timer 2.0
  "How long (in idle seconds) until incremental loading starts.

 Set this to nil to disable incremental loading.")

(defvar elemacs-incremental-idle-timer 0.75
  "How long (in idle seconds) in between incrementally loading packages.")

(defvar elemacs-incremental-load-immediately (daemonp)
  "If non-nil, load all incrementally deferred packages immediately at startup.")

(defun elemacs-load-packages-incrementally (packages &optional now)
  "Registers PACKAGES to be loaded incrementally.

  If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
  intervals."
  (if (not now)
      (setq elemacs-incremental-packages (append elemacs-incremental-packages packages ))
    (while packages
      (let* ((gc-cons-threshold most-positive-fixnum)
             (req (pop packages)))
        (unless (featurep req)
          (message "Incrementally loading %s" req)
          (condition-case-unless-debug e
              (or (while-no-input
                    ;; If `default-directory' is a directory that doesn't exist
                    ;; or is unreadable, Emacs throws up file-missing errors, so
                    ;; we set it to a directory we know exists and is readable.
                    (let ((default-directory user-emacs-directory)
                          (inhibit-message t)
                          file-name-handler-alist)
                      (require req nil t))
                    t)
                  (push req packages))
            (error
             (message "Failed to load %S package incrementally, because: %s"
                      req e)))
          (if (not packages)
              (message "Finished incremental loading")
            (run-with-idle-timer elemacs-incremental-idle-timer
                                 nil #'elemacs-load-packages-incrementally
                                 packages t)
            (setq packages nil)))))))

(defun elemacs-load-packages-incrementally-h ()
  "Begin incrementally loading packages in `elemacs-incremental-packages'.

If this is a daemon session, load them all immediately instead."
  (if elemacs-incremental-load-immediately
      (mapc #'require (cdr elemacs-incremental-packages))
    (when (numberp elemacs-incremental-first-idle-timer)
      (run-with-idle-timer elemacs-incremental-first-idle-timer
                           nil #'elemacs-load-packages-incrementally
                           (cdr elemacs-incremental-packages) t))))

(add-hook 'emacs-startup-hook #'elemacs-load-packages-incrementally-h)

在我的电脑上测试,进入 org-mode 时的资源消耗可以减少 15%。

另外 doom 还给 use-package 加了 :defer-incrementally 关键字:

  (defalias 'use-package-normalize/:defer-incrementally #'use-package-normalize-symlist)
  (defun use-package-handler/:defer-incrementally (name _keyword targets rest state)
    (use-package-concat
     `((doom-load-packages-incrementally
        ',(if (equal targets '(t))
              (list name)
            (append targets (list name)))))
     (use-package-process-keywords name rest state)))

但我不打算继续用 use-package 了,因此就没继续深入,感兴趣的可以自己去看看。

6 个赞

另附目前的优化成果:

2 个赞

general.el 作者抽象了一个包,可以参考下:GitHub - emacs-magus/once: Extra init.el deferred evaluation utilities

具体讨论:Rewrite · Issue #497 · noctuid/general.el · GitHub

2 个赞