3

When I call commands such as helm-apropos and custom Org agendas there is often a noticeable delay between the key sequence and the results presented. How can I speed up these commands to respond instantly to being called? I'm most interested in methods that can be applied to any Emacs commands, but optimizations for helm and org-mode would also be appreciated.

Please note - I am not asking about Emacs startup time. I would be happy for my Emacs startup to take 10x longer if I could make all Emacs commands instant.

6
  • Did you consider that there could be no intentional delay between the key stroke being registered and the command finishing, but just a lengthy computation going on?
    – wasamasa
    Jul 20, 2015 at 13:55
  • Do these commands take long time only first time they are called or every time? Jul 20, 2015 at 14:07
  • 1
    Org Agenda commands will take longer if there are skip functions and/or criteria that force evaluation of the header and the time-stamp. You can create your own custom org-agenda data gathering function to be more efficient. You can limit the org-agenda-files to only the ones you really need to query, or put everything in just one master file. Think of it like this: goto to the top of each file, then go to the first heading, then go to the time-stamp, then maybe skip, push data, then go to the next heading and repeat, push data, repeat until end of file, then move on to next file and . .
    – lawlist
    Jul 20, 2015 at 14:23
  • The function org-agenda-list will repeat the above-process for every day within the span -- (while (setq d (pop day-numbers)) . . . -- e.g., 31 days equals 31 loops through each org-agenda-file -- (while (setq file (pop files)) (catch 'nextfile . . . -- with the same dance of headline/time-stamp/skip-maybe. If you were to create your own custom function, you may wish to consider gathering all of the data in one fell swoop, and programmatically manipulate/sort all of the data rather than sweeping through the buffer multiple times.
    – lawlist
    Jul 20, 2015 at 14:45
  • The following link contains an example that bypasses org-agenda-list and goes straight to org-agenda-get-day-entries to gather data: emacs.stackexchange.com/a/12563/2287 If you choose to write your own function to programmatically manipulate/sort all the data rather than sweep through the buffer multiple times, then you'll need to modify the functions used by org-agenda-get-day-entries -- e.g., org-agenda-get-todos; org-agenda-get-blocks; org-agenda-get-timestamps; org-agenda-get-sexps; org-agenda-get-scheduled; org-agenda-get-progress; org-agenda-get-deadlines.
    – lawlist
    Jul 20, 2015 at 14:58

1 Answer 1

7

helm-apropos is indeed pretty slow. I think it comes from joining 5 very large lists:

helm-apropos-function-list
;; =>
;; (helm-def-source--emacs-commands
;;  helm-def-source--emacs-functions
;;  helm-def-source--eieio-classes
;;  helm-def-source--eieio-generic
;;  helm-def-source--emacs-variables
;;  helm-def-source--emacs-faces
;;  helm-def-source--helm-attributes)

You can speed it up by generating one huge list right off the bat:

(defun helm-fast-apropos ()
      (interactive)
      (helm :sources
            '((name . "apropos: ")
              (candidates .
               (lambda ()
                 (all-completions "" obarray
                                  (lambda (x)
                                    (or
                                     ;; commands
                                     (commandp x)
                                     ;; functions
                                     (fboundp x)
                                     ;; EIEIO class
                                     (class-p x)
                                     ;; EIEIO generic
                                     (generic-p x)
                                     ;; variable
                                     (and (boundp x) (not (keywordp x))))))))
              (action . (lambda (x)
                        (setq x (intern x))
                        (cond ((boundp x)
                               (describe-variable x))
                              ((fboundp x)
                               (describe-function x))))))))

I also just had a quick look at the ivy equivalent of the above, I think it's around 0.2s faster:

(defun ivy-apropos ()
  (interactive)
  (ivy-read "apropos: "
            (all-completions "" obarray
                             (lambda (x)
                               (or
                                ;; commands
                                (commandp x)
                                ;; functions
                                (fboundp x)
                                ;; EIEIO class
                                (class-p x)
                                ;; EIEIO generic
                                (generic-p x)
                                ;; variable
                                (and (boundp x) (not (keywordp x))))))
            :keymap counsel-describe-map
            :preselect (counsel-symbol-at-point)
            :action (lambda (x)
                      (setq x (intern x))
                      (cond ((boundp x)
                             (describe-variable x))
                            ((fboundp x)
                             (describe-function x))))))

Edit: typical approach for caching

Very simple: store the whole collection into a variable, and hope that it doesn't need to be recomputed; recompute manually when needed (usually, when new packages are loaded):

(defvar helm-fast-apropos-cache nil)
(defun helm-update-apropos-cache ()
  (interactive)
  (setq helm-fast-apropos-cache
        (all-completions "" obarray
                         (lambda (x)
                           (or
                            ;; commands
                            (commandp x)
                            ;; functions
                            (fboundp x)
                            ;; EIEIO class
                            (class-p x)
                            ;; EIEIO generic
                            (generic-p x)
                            ;; variable
                            (and (boundp x) (not (keywordp x))))))))

(defun helm-fast-apropos ()
  (interactive)
  (helm :sources
        (quote ((name . "apropos: ")
                (candidates .
                            (lambda ()
                              (or helm-fast-apropos-cache
                                  (helm-update-apropos-cache))))
                (action . (lambda (x)
                            (setq x (intern x))
                            (cond ((boundp x)
                                   (describe-variable x))
                                  ((fboundp x)
                                   (describe-function x)))))))))

This approach saves only fraction of a second in this case, so it might not be worth it.

6
  • 1
    If the suggested speedup isn't enough, it's possible to add caching. It will be even faster, but has the drawback of having to refresh the cache sometimes.
    – abo-abo
    Jul 20, 2015 at 14:13
  • I would be very interested in caching. Is there a general approach that would allow me to cache any command?
    – Conor
    Jul 20, 2015 at 15:34
  • 1
    Well, if commands were to be purely functional, you could use memoization, but they are not. So, get in touch with the package authors and figure out what's going wrong for performance to be so bad.
    – wasamasa
    Jul 20, 2015 at 19:13
  • Thank you for the detail on caching. I couldn't get your helm apropos function to work, as I don't think you can supply action as an argument to helm.
    – Conor
    Jul 20, 2015 at 20:36
  • 1
    @Conor, just fixed helm-fast-apropos.
    – abo-abo
    Jul 21, 2015 at 7:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.