Subject: Re: bug#78637: 30.1.90; Calling setopt during init loads cus-start over and over

From: Eli Zaretskii <[email-masked]>
Date: Fri, 30 May 2025 10:24:08 +0300

> Cc: [email-masked]
> Date: Thu, 29 May 2025 16:57:22 -0400
> From:  Aaron Zeng via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <[email-masked]>
> 
> While trying to improve Emacs startup time at my site, I noticed that
> cus-start was being loaded repeatedly, each time setopt was called.
> It seems that, when called during site-start or loading the user's
> init file, setopt (which eventually calls custom-load-symbol) requires
> cus-start, ignoring errors---and loading cus-start at that moment does
> indeed error.
> 
> As a result of this ignored error, cus-start is loaded from scratch
> each time (require 'cus-start) is evaluated.  This adds something like
> 12ms of startup time to Emacs batch commands at my site, since we have
> about 25 setopt calls in site-start, and each one takes an extra 500us
> or so.
> 
> I performed a quick experiment, with:
> 
>     emacs -Q --batch --init-directory=example-init --user ""
> 
> where example-init/init.el contains just:
> 
>     (dotimes (_ 25)
>       (setopt make-backup-files nil))
> 
> Running the above command under hyperfine shows that the runtime is
> about 82ms on my machine.
> 
> Prepending this code to example-init/init.el:
> 
>     (ignore-errors
>       (require 'cus-start))
>     (push 'cus-start features)
> 
> drops the runtime of the command to 75ms.  (I believe this experiment
> might underestimate the true impact of repeatedly loading cus-start
> due to caching effects.)

I don't understand: cus-start is preloaded (see lisp/loadup.el), so
"(require 'cus-start)" should be (almost) a no-op.  Do you have any
evidence that cus-start.el is indeed being loaded under this recipe?
If so, can you present that evidence?  (And no, the fact that startup
time goes down doesn't mean that loading cus-start is the reason, it
could be something else.)

> I believe this can be fixed with the following patch.  On my machine,
> this patch reduces the runtime of the original init file to 75ms and
> causes the prepended code to make no difference to the runtime.
> 
> diff --git a/lisp/cus-start.el b/lisp/cus-start.el
> index 91cc6e22152..e82e97e87ca 100644
> --- a/lisp/cus-start.el
> +++ b/lisp/cus-start.el
> @@ -934,7 +934,7 @@ minibuffer-prompt-properties--setter
>        ;; This is used by describe-variable.
>        (if version (put symbol 'custom-version version))
>        ;; Don't re-add to custom-delayed-init-variables post-startup.
> -      (unless after-init-time
> +      (when (listp custom-delayed-init-variables)
>  	;; Note this is the _only_ initialize property we handle.
>  	(if (eq (cadr (memq :initialize rest)) #'custom-initialize-delay)
>  	    ;; These vars are defined early and should hence be initialized

I think this patch changes behavior, so it is not TRT.  But before we
discuss how to fix the problem, let's be sure we understand the
problem and its reason(s).