translate done: 20171119 Advanced Techniques for Reducing Emacs Startup Time.md

This commit is contained in:
darksun 2019-03-16 17:29:49 +08:00
parent ee7b760f97
commit 570f9a03e1
2 changed files with 256 additions and 252 deletions

View File

@ -1,252 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (lujun9972)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Advanced Techniques for Reducing Emacs Startup Time)
[#]: via: (https://blog.d46.us/advanced-emacs-startup/)
[#]: author: (Joe Schafer https://blog.d46.us/)
Advanced Techniques for Reducing Emacs Startup Time
======
Six techniques to reduce Emacs startup time by the author of the [Emacs Start Up Profiler][1].
tl;dr: Do these steps:
1. Profile with Esup.
2. Adjust the garbage collection threshold.
3. Autoload **everything** with use-package.
4. Avoid helper functions which cause eager loads.
5. See my Emacs [config][2] for an example.
### From .emacs.d Bankruptcy to Now
I recently declared my third .emacs.d bankruptcy and finished the fourth iteration of my Emacs configuration. The evolution was:
1. Copy and paste elisp snippets into `~/.emacs` and hope it works.
2. Adopt a more structured approach with `el-get` to manage dependencies.
3. Give up and outsource to Spacemacs.
4. Get tired of Spacemacs intricacies and rewrite with `use-package`.
This article is a collection of tips collected during the 3 rewrites and from creating the Emacs Start Up Profiler. Many thanks to the teams behind Spacemacs, use-package and general. Without these dedicated voluteers, this task would be vastly more difficult.
### But What About Daemon Mode
Before we get started, let me acknowledge the common retort when optimizing Emacs: “Emacs is meant to run as a daemon so youll only start it once.” Thats all well and good except:
* Fast things feel nicer.
* When customizing Emacs, you sometimes get into weird states that can be hard to recover from without restarting. For example, if you add a slow `lambda` function to your `post-command-hook`, its tough to remove it.
* Restarting Emacs helps verify that customization will persist between sessions.
### 1\. Establish the Current and Best Possible Start Up Time
The first step is to measure the current start up time. The easy way is to display the information at startup which will show progress through the next steps.
```
(add-hook 'emacs-startup-hook
(lambda ()
(message "Emacs ready in %s with %d garbage collections."
(format "%.2f seconds"
(float-time
(time-subtract after-init-time before-init-time)))
gcs-done)))
```
Second, measure the best possible startup speed so you know whats possible. Mine is 0.3 seconds.
```
emacs -q --eval='(message "%s" (emacs-init-time))'
;; For macOS users:
open -n /Applications/Emacs.app --args -q --eval='(message "%s" (emacs-init-time))'
```
### 2\. Profile Emacs Startup for Easy Wins
The [Emacs StartUp Profiler][1] (ESUP) will give you detailed metrics for top-level expressions.
![esup.png][3]
Figure 1:
Emacs Start Up Profiler Screenshot
WARNING: Spacemacs users, ESUP currently chokes on the Spacemacs init.el file. Follow <https://github.com/jschaf/esup/issues/48> for updates.
### 3\. Set the Garbage Collection Threshold Higher during Startup
This saves about ****0.3 seconds**** on my configuration.
The default value for Emacs is 760kB which is extremely conservative on a modern machine. The real trick is to lower it back to something reasonable after initialization. This saves about 0.3 seconds on my init files.
```
;; Make startup faster by reducing the frequency of garbage
;; collection. The default is 800 kilobytes. Measured in bytes.
(setq gc-cons-threshold (* 50 1000 1000))
;; The rest of the init file.
;; Make gc pauses faster by decreasing the threshold.
(setq gc-cons-threshold (* 2 1000 1000))
```
### 4\. Never require anything; autoload with use-package instead
The best way to make Emacs faster is to do less. Running `require` eagerly loads the underlying source file. Its rare the youll need functionality immediately at startup time.
With [`use-package`][4], you declare which features you need from a package and `use-package` does the right thing. Heres what it looks like:
```
(use-package evil-lisp-state ; the Melpa package name
:defer t ; autoload this package
:init ; Code to run immediately.
(setq evil-lisp-state-global nil)
:config ; Code to run after the package is loaded.
(abn/define-leader-keys "k" evil-lisp-state-map))
```
To see what packages Emacs currently has loaded, examine the `features` variable. For nice output see [lpkg explorer][5] or my variant in [abn-funcs-benchmark.el][6]. The output looks like:
```
479 features currently loaded
- abn-funcs-benchmark: /Users/jschaf/.dotfiles/emacs/funcs/abn-funcs-benchmark.el
- evil-surround: /Users/jschaf/.emacs.d/elpa/evil-surround-20170910.1952/evil-surround.elc
- misearch: /Applications/Emacs.app/Contents/Resources/lisp/misearch.elc
- multi-isearch: nil
- <many more>
```
### 5\. Avoid Helper Functions to Set Up Modes
Often, Emacs packages will suggest running a helper function to set up keybindings. Heres a few examples:
* `(evil-escape-mode)`
* `(windmove-default-keybindings) ; Sets up keybindings.`
* `(yas-global-mode 1) ; Complex snippet setup.`
Rewrite these with use-package to improve startup speed. These helper functions are really just sneaky ways to trick you into eagerly loading packages before you need them.
As an example, heres how to autoload `evil-escape-mode`.
```
;; The definition of evil-escape-mode.
(define-minor-mode evil-escape-mode
(if evil-escape-mode
(add-hook 'pre-command-hook 'evil-escape-pre-command-hook)
(remove-hook 'pre-command-hook 'evil-escape-pre-command-hook)))
;; Before:
(evil-escape-mode)
;; After:
(use-package evil-escape
:defer t
;; Only needed for functions without an autoload comment (;;;###autoload).
:commands (evil-escape-pre-command-hook)
;; Adding to a hook won't load the function until we invoke it.
;; With pre-command-hook, that means the first command we run will
;; load evil-escape.
:init (add-hook 'pre-command-hook 'evil-escape-pre-command-hook))
```
For a much trickier example, consider `org-babel`. The common recipe is:
```
(org-babel-do-load-languages
'org-babel-load-languages
'((shell . t)
(emacs-lisp . nil)))
```
This is bad because `org-babel-do-load-languages` is defined in `org.el`, which is over 24k lines of code and takes about 0.2 seconds to load. After examining the source code, `org-babel-do-load-languages` is simply requiring the `ob-<lang>` package like so:
```
;; From org.el in the org-babel-do-load-languages function.
(require (intern (concat "ob-" lang)))
```
In the `ob-<lang>.el`, theres only two methods we care about, `org-babel-execute:<lang>` and `org-babel-expand-body:<lang>`. We can autoload the org-babel functionality instead of `org-babel-do-load-languages` like so:
```
;; Avoid `org-babel-do-load-languages' since it does an eager require.
(use-package ob-python
:defer t
:ensure org-plus-contrib
:commands (org-babel-execute:python))
(use-package ob-shell
:defer t
:ensure org-plus-contrib
:commands
(org-babel-execute:sh
org-babel-expand-body:sh
org-babel-execute:bash
org-babel-expand-body:bash))
```
### 6\. Defer Packages you dont need Immediately with Idle Timers
This saves about ****0.4 seconds**** for the 9 packages I defer.
Some packages are useful and you want them available soon, but are not essential for immediate editing. These modes include:
* `recentf`: Saves recent files.
* `saveplace`: Saves point of visited files.
* `server`: Starts Emacs daemon.
* `autorevert`: Automatically reloads files that changed on disk.
* `paren`: Highlight matching parenthesis.
* `projectile`: Project management tools.
* `whitespace`: Highlight trailing whitespace.
Instead of requiring these modes, ****load them after N seconds of idle time****. I use 1 second for the more important packages and 2 seconds for everything else.
```
(use-package recentf
;; Loads after 1 second of idle time.
:defer 1)
(use-package uniquify
;; Less important than recentf.
:defer 2)
```
### Optimizations that arent Worth It
Dont bother byte-compiling your personal Emacs files. It saved about 0.05 seconds. Byte compiling causes difficult to debug errors when the source file gets out of sync with compiled file.
--------------------------------------------------------------------------------
via: https://blog.d46.us/advanced-emacs-startup/
作者:[Joe Schafer][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://blog.d46.us/
[b]: https://github.com/lujun9972
[1]: https://github.com/jschaf/esup
[2]: https://github.com/jschaf/dotfiles/blob/master/emacs/start.el
[3]: https://blog.d46.us/images/esup.png
[4]: https://github.com/jwiegley/use-package
[5]: https://gist.github.com/RockyRoad29/bd4ca6fdb41196a71662986f809e2b1c
[6]: https://github.com/jschaf/dotfiles/blob/master/emacs/funcs/abn-funcs-benchmark.el

View File

@ -0,0 +1,256 @@
[#]: collector: (lujun9972)
[#]: translator: (lujun9972)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Advanced Techniques for Reducing Emacs Startup Time)
[#]: via: (https://blog.d46.us/advanced-emacs-startup/)
[#]: author: (Joe Schafer https://blog.d46.us/)
降低 Emacs 启动时间的高级技术
======
[Emacs Start Up Profiler][1] 的作者教你六项技术减少 Emacs 启动时间。
简而言之:做下面几个步骤:
1。使用 Esup 进行性能检测。
2。调整垃圾回收的阀值。
3。使用 usge-page 来自动(延迟)加载所有东西。
4。不要使用会引起立即加载的辅助函数。
5。参考我的 [配置 ][2]。
### 从 .emacs.d 破产到现在
我最近宣布了第三次 .emacs.d 破产并完成了第四次 Emacs 配置的迭代。演化过程为:
1。拷贝并粘贴 elisp 片段到 `~/.emacs` 中,希望它能工作。
2。借助 `el-get` 来以更结构化的方式来管理依赖关系。
3。放弃自己从零配置以 Spacemacs 为基础。
4。厌倦了 Spacemacs 的复杂性,基于 `use-package` 重写配置。
本文汇聚了三次重写和创建 `Emacs Start Up Profiler` 过程中的技巧。
非常感谢 Spacemacsuse-package 等背后的团队。
没有这些无私的志愿者,这项任务将会困难得多。
### 不过守护进程模式又如何呢
在我们开始之前,让我声明一下常见的反对优化 Emacs 的观念“Emacs 旨在作为守护进程来运行的,因此你只需要运行一次而已。”
这个观点很好,只不过:
- 速度总是越快越好
- 配置 Emacs 时,可能会有不得不通过重启 Emacs 的情况。例如,你可能为 `post-command-hook` 添加了一个运行缓慢的 `lambda` 函数,很难删掉它。
- 重启 Emacs 能帮你验证不同会话之间是否还能保留配置
### 估算当前以及最佳的启动时间
第一步是衡量当前的启动时间。最简单的方法就是在启动时显示后续步骤进度的信息。
```
(add-hook 'emacs-startup-hook
(lambda ()
(message "Emacs ready in %s with %d garbage collections."
(format "%.2f seconds"
(float-time
(time-subtract after-init-time before-init-time)))
gcs-done)))
```
第二部,衡量最佳的启动速度,以便了解可能的情况。我的是 0.3 秒。
```
emacs -q --eval='(message "%s" (emacs-init-time))'
;; For macOS users:
open -n /Applications/Emacs.app --args -q --eval='(message "%s" (emacs-init-time))'
```
### 2。检测 Emacs 启动指标对你大有帮助
[Emacs StartUp Profiler][1] (ESUP) 将会给你顶层语句执行的详细指标。
![esup.png][3]
图 1: Emacs Start Up Profiler 截图
警告Spacemacs 用户需要注意ESUP 目前与 Spacemacs 的 init.el 文件有冲突。遵照 <https://github.com/jschaf/esup/issues/48> 上说的进行升级。
### 启动时调高垃圾回收的阀值
这为我节省了 **0.3 秒**
Emacs 默认值是 760kB这在现代机器看来及其的保守。
真正的诀窍在于初始化完成后再把它降到合理的水平。
这为了节省了 0.3 秒
```
;; Make startup faster by reducing the frequency of garbage
;; collection. The default is 800 kilobytes. Measured in bytes.
(setq gc-cons-threshold (* 50 1000 1000))
;; The rest of the init file.
;; Make gc pauses faster by decreasing the threshold.
(setq gc-cons-threshold (* 2 1000 1000))
```
### 4。不要 require 任何东西,转而使用 use-package 来自动加载
让 Emacs 变坏的最好方法就是减少要做的事情。`require` 会立即加载源文件。
但是很少会出现需要在启动阶段就立即需要这些功能的。
在 [`use-package`][4] 中你只需要声明好需要哪个包中的哪个功能,`use-package` 就会帮你完成正确的事情。
它看起来是这样的:
```
(use-package evil-lisp-state ; the Melpa package name
:defer t ; autoload this package
:init ; Code to run immediately.
(setq evil-lisp-state-global nil)
:config ; Code to run after the package is loaded.
(abn/define-leader-keys "k" evil-lisp-state-map))
```
可以通过查看 `features` 变量来查看 Emacs 现在加载了那些包。
想要更好看的输出可以使用 [lpkg explorer][5] 或者我在 [abn-funcs-benchmark.el][6] 中的变体。
输出看起来类似这样的:
```
479 features currently loaded
- abn-funcs-benchmark: /Users/jschaf/.dotfiles/emacs/funcs/abn-funcs-benchmark.el
- evil-surround: /Users/jschaf/.emacs.d/elpa/evil-surround-20170910.1952/evil-surround.elc
- misearch: /Applications/Emacs.app/Contents/Resources/lisp/misearch.elc
- multi-isearch: nil
- <many more>
```
### 5。不要使用辅助函数来设置模式
通常Emacs packages 会建议通过运行一个辅助函数来设置键绑定。下面是一些例子:
* `(evil-escape-mode)`
* `(windmove-default-keybindings) ; 设置快捷键。`
* `(yas-global-mode 1) ;复杂的片段配置。`
可以通过 use-package 来对此进行重构以提高启动速度。这些辅助函数只会让你立即加载那些尚用不到的 package。
下面这个例子告诉你如何自动加载 `evil-escape-mode`
```
;; The definition of evil-escape-mode.
(define-minor-mode evil-escape-mode
(if evil-escape-mode
(add-hook 'pre-command-hook 'evil-escape-pre-command-hook)
(remove-hook 'pre-command-hook 'evil-escape-pre-command-hook)))
;; Before:
(evil-escape-mode)
;; After:
(use-package evil-escape
:defer t
;; Only needed for functions without an autoload comment (;;;###autoload).
:commands (evil-escape-pre-command-hook)
;; Adding to a hook won't load the function until we invoke it.
;; With pre-command-hook, that means the first command we run will
;; load evil-escape.
:init (add-hook 'pre-command-hook 'evil-escape-pre-command-hook))
```
下面来看一个关于 `org-babel` 的例子,这个例子更为复杂。我们通常的配置时这样的:
```
(org-babel-do-load-languages
'org-babel-load-languages
'((shell . t)
(emacs-lisp . nil)))
```
这种不是个好的配置,因为 `org-babel-do-load-languages` 定义在 `org.el` 中,而该文件有超过 2 万 4 千行的代码,需要花 0.2 秒来加载。
通过查看源代码可以看到 `org-babel-do-load-languages` 仅仅只是加载 `ob-<lang>` 包而已,像这样:
```
;; From org.el in the org-babel-do-load-languages function.
(require (intern (concat "ob-" lang)))
```
而在 `ob-<lang>.el` 文件中,我们只关心其中的两个方法 `org-babel-execute:<lang>``org-babel-expand-body:<lang>`
我们可以延时加载 org-babel 相关功能而无需调用 `org-babel-do-load-languages`,像这样:
```
;; Avoid `org-babel-do-load-languages' since it does an eager require.
(use-package ob-python
:defer t
:ensure org-plus-contrib
:commands (org-babel-execute:python))
(use-package ob-shell
:defer t
:ensure org-plus-contrib
:commands
(org-babel-execute:sh
org-babel-expand-body:sh
org-babel-execute:bash
org-babel-expand-body:bash))
```
### 6。使用惰性定时器 (idle timer) 来推迟加载非立即需要的包
我推迟加载了 9 个包,这帮我节省了 **0.4 秒**
有些包特别有用,你希望可以很快就能使用它们,但是它们本身在 Emacs 启动过程中又不是必须的。这些 mode 包括:
- `recentf`: 保存最近的编辑过的那些文件。
- `saveplace`: 保存访问过文件的光标位置。
- `server`: 开启 Emacs 守护进程。
- `autorevert`: 自动重载被修改过的文件。
- `paren`: 高亮匹配的括号。
- `projectile`: 项目管理工具。
- `whitespace`: 高亮行尾的空格。
不要 require 这些 mode** 而是等到空闲 N 秒后再加载它们**。
我在 1 秒后加载那些比较重要的包,在 2 秒后加载其他所有的包。
```
(use-package recentf
;; Loads after 1 second of idle time.
:defer 1)
(use-package uniquify
;; Less important than recentf.
:defer 2)
```
### 不值得的优化
不要费力把你的 Emacs 配置文件编译成字节码了。这只节省了大约 0.05 秒。
把配置文件编译成字节码可能导致源文件与编译后的文件不匹配从而导致难以出现错误调试。
--------------------------------------------------------------------------------
via: https://blog.d46.us/advanced-emacs-startup/
作者:[Joe Schafer][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://blog.d46.us/
[b]: https://github.com/lujun9972
[1]: https://github.com/jschaf/esup
[2]: https://github.com/jschaf/dotfiles/blob/master/emacs/start.el
[3]: https://blog.d46.us/images/esup.png
[4]: https://github.com/jwiegley/use-package
[5]: https://gist.github.com/RockyRoad29/bd4ca6fdb41196a71662986f809e2b1c
[6]: https://github.com/jschaf/dotfiles/blob/master/emacs/funcs/abn-funcs-benchmark.el