I try use my own configuration and was amazed by the startup speed of doom eamcs. Why is doom emacs so fast on startup? What optimization did you make?
I noticed that all packages are installed before startup. Is it because package checking is expensive?
Yeah it is faster than Prelude and Spacemacs!!!!!!
Doom employs a few techniques to achieve its speed (as well as a ton of premature optimization). Here are some of its more important secrets:
The garbage collector eats up a lot of time during startup, so turn up its memory threshold to prevent it from getting triggered:
(defvar last-file-name-handler-alist file-name-handler-alist)
(setq gc-cons-threshold 402653184
gc-cons-percentage 0.6
file-name-handler-alist nil)
;; ... your whole emacs config here ...
;; after startup, it is important you reset this to some reasonable default. A large
;; gc-cons-threshold will cause freezing and stuttering during long-term
;; interactive use. I find these are nice defaults:
(add-hook! 'emacs-startup-hook
(setq gc-cons-threshold 16777216
gc-cons-percentage 0.1
file-name-handler-alist last-file-name-handler-alist))
(Unrelated to GC, but I also temporarily unset file-name-handler-alist, which Emacs looks through every time it loads a file; this gives me a modest 90-120ms boost).
Every time you load or require a package, Emacs does an O(n) equal lookup on load-path. Little you can do about this, but where I can I let-bind load-path to a subset of itself (like in doom-initialize). I also use load! to load all of Doom's module files, which expands to (load ABSOLUTE_PATH nil t) without a load-path lookup.
package.el initialization loads every package autoloads file and populates load-path. That makes (package-initialize) a great target for premature optimizers like me! Besides, i don't need those autoloads (I define them manually), so turn it off:
(setq package-enable-at-startup nil ; don't auto-initialize!
;; this tells package.el not to add those pesky customized variable settings
;; at the end of your init.el
package--init-file-ensured t)
And then I populate load-path manually.
make compile-core. You can byte-compile the whole config with make compile but that takes much longer and you get significantly diminished returns.;; -*- lexical-binding: t; -*- to the top of your elisp files. This _can_ cause code breakage if your code depends on dynamic variables, but I've written Doom not to.cl-loop or dolist over mapcar and mapc. Lambdas add a little overhead each time they're executed in loops (and the byte-compiler has trouble inlining them; especially when lexical-binding is on!).Phew! That's not everything, but those are the most important ones. I hope that helps!
This answer is perfect candidate for Doom's wiki - either under FAQ or under new page.
(I know that everyone can edit wiki, but feels little weird to add something that I don't entirely understand)
Your answer is awesome! Thanks 馃槃
@AloisJanicek agreed! Actually, this was lifted (and paraphrased) from a draft for the wiki, so it'll be in there soon.
And done!
Most helpful comment
Doom employs a few techniques to achieve its speed (as well as a ton of premature optimization). Here are some of its more important secrets:
The garbage collector eats up a lot of time during startup, so turn up its memory threshold to prevent it from getting triggered:
(Unrelated to GC, but I also temporarily unset
file-name-handler-alist, which Emacs looks through every time it loads a file; this gives me a modest 90-120ms boost).Every time you
loadorrequirea package, Emacs does an O(n)equallookup onload-path. Little you can do about this, but where I can I let-bindload-pathto a subset of itself (like in doom-initialize). I also useload!to load all of Doom's module files, which expands to(load ABSOLUTE_PATH nil t)without aload-pathlookup.package.el initialization loads every package autoloads file and populates
load-path. That makes(package-initialize)a great target for premature optimizers like me! Besides, i don't need those autoloads (I define them manually), so turn it off:And then I populate
load-pathmanually.make compile-core. You can byte-compile the whole config withmake compilebut that takes much longer and you get significantly diminished returns.;; -*- lexical-binding: t; -*-to the top of your elisp files. This _can_ cause code breakage if your code depends on dynamic variables, but I've written Doom not to.cl-loopordolistovermapcarandmapc. Lambdas add a little overhead each time they're executed in loops (and the byte-compiler has trouble inlining them; especially whenlexical-bindingis on!).Phew! That's not everything, but those are the most important ones. I hope that helps!