As an Emacs configuration grows, startup time can gradually increase. Measuring that increase accurately makes it easier to identify regressions. The majority of Emacs users use emacs-init-time. However, this built-in function does not measure all the work performed during startup.
Emacs startup proceeds through several stages: it loads the early init and regular init files, processes command-line options, sets after-init-time, runs emacs-startup-hook, performs additional initial-frame setup such as applying frame parameters from the configuration, and then runs window-setup-hook after the initial frame parameters have been configured. This makes window-setup-hook a useful place to record a broader startup-time measurement.
For a more accurate startup-time measurement, add the following Emacs Lisp code to early-init.el or init.el:
;; Emacs startup proceeds through several stages: it loads the early init and
;; regular init files, processes command-line options, sets after-init-time,
;; runs emacs-startup-hook, performs additional initial-frame setup such as
;; applying frame parameters from the configuration, and then runs
;; window-setup-hook after the initial frame parameters have been configured.
;; This makes window-setup-hook a useful place to record a broader startup-time
;; measurement.
;;
;; URL: https://www.jamescherti.com/measuring-emacs-startup-time/
(defvar my-recorded-startup-time-message nil
"Stores the formatted string of the Emacs startup metrics.")
(defun my-record-startup-time ()
"Calculate and record the elapsed startup time.
This function records the time when `window-setup-hook' runs."
(setq my-recorded-startup-time-message
(format "Emacs loaded in %.2f seconds (Init time: %.2fs) with %d garbage collections."
(float-time (time-since before-init-time))
(float-time (time-subtract after-init-time before-init-time))
gcs-done))
;; Output to the *Messages* buffer during the initial launch
(message "%s" my-recorded-startup-time-message))
(defun my-display-startup-time ()
"Display the previously recorded Emacs startup time in the echo area."
(interactive)
(if my-recorded-startup-time-message
(message "%s" my-recorded-startup-time-message)
(message "Startup time was not recorded.")))
;; Read startup summary:
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Startup-Summary.html
(add-hook 'window-setup-hook #'my-record-startup-time 99)
This provides a broader measurement than M-x emacs-init-time because it continues measuring after after-init-time has been set, including subsequent startup processing through window-setup-hook.
A depth of 99 places the function near the end of window-setup-hook. This makes the measurement less likely to omit work performed by other functions on the same hook.
The variable gcs-done tracks the total number of garbage collections during the session. Because this function formats and saves the message string during window-setup-hook, it locks in the exact number of garbage collections triggered during the startup phase, giving you a metric of memory allocation bottlenecks during initialization. Ideally, it should be 0 or 1.
Capturing the time the UI finishes rendering provides a reliable metric to track down performance regressions. Once Emacs has finished loading, you can retrieve the recorded startup time at any point during your session. Execute M-x my-display-startup-time to print the exact initialization metrics in the echo area. This interactive command ensures the startup data remains accessible even if the initial message is cleared from the screen by subsequent buffer activity.
But why do we care? I start my emacs once every two months or so.. unless I don't need to reboot or turn the macine off, emacs is running as a demon
Hello Christian,
That is a fair point. If you rely on a daemon and only restart every couple of months, raw startup time won't really impact your daily usage.
However, I still treat startup speed as a key indicator of overall configuration health. I've optimized mine down to 0.23 seconds alongside 151 packages, and I've found the benefits extend well beyond the initial cold boot.
For example, whenever I am modifying my Elisp configuration or working on package development, I need to restart Emacs frequently to test changes in a clean state. Waiting several seconds for every reload slows down the debugging process.
More importantly, initialization time acts as a reliable canary for regressions. A slow startup usually indicates an over-reliance on eager evaluation rather than properly using autoloads and deferred execution. Enforcing a fast startup keeps the configuration modular and keeps the baseline memory footprint low. If my startup time suddenly spikes, it immediately alerts me to underlying issues, like blocking network requests, synchronous shell calls, or garbage collection bottlenecks, that I might otherwise overlook.
Finally, daemons are not always practical in every environment. When jumping into a quick SSH session on a remote server, it is helpful to have Emacs open instantly without needing to manage background processes.