Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

describe-variable is slow with many variables due to buffer switching in predicate #40

Closed
jacksonludwig opened this issue May 19, 2021 · 21 comments

Comments

@jacksonludwig
Copy link

I noticed that when using vertico in a situation where there are many results (e.g. C-h v lists 12 thousand+ items for me), there is noticeable lag when typing until the search is narrowed enough.

Is this just emacs's fault? Or is there a way around it somehow?

Thank you for your efforts on this package.

@minad
Copy link
Owner

minad commented May 19, 2021

C-h f has 13k items for me and it feels reasonably fast. I am not on a fast/new machine here. There is not much we can do about this except disabling sorting above a threshold (I had this at some point, but removed it) or special casing static completion tables (Selectrum does this) like the one for symbols/functions/variables. Vertico is already optimized, but you can take a look at a profiler run to see where the bottleneck lies. I want to avoid special casing in Vertico. You can try Selectrum which has additional optimizations of static tables.

EDIT: Two questions: Which completion style do you use and with which system do you compare, Selectrum? I use Orderless with the default literal and regexp matching styles with my custom style dispatcher. Maybe you use the Emacs flex style, which is slower because the flex regexps are more expensive to match.

@minad minad closed this as completed May 19, 2021
@jacksonludwig
Copy link
Author

@minad Thanks for the info. I just tried C-h f, which actually had 30k results (vs my earlier test of C-h v which had 12k) yet it was much smoother with no noticeable lag. So maybe it is not the number of candidates but something else with how variable help is displayed?

I am using orderless (I am essentially using exactly what is in the readme in this repo).

@minad
Copy link
Owner

minad commented May 19, 2021

Ah okay, there could be some issue at play here specifically with the variable completion table only. The variable table has some kind of bad logic inside which expects the table to be called in a certain context. Selectrum had the same problem. I will investigate.

@minad minad reopened this May 19, 2021
@daviwil
Copy link
Sponsor

daviwil commented May 19, 2021

Are you also using Marginalia with full annotations turned on? I've got 20k+ results in describe-variable and listing/filtering feel instant (I am using Emacs 28 native-comp though).

@minad
Copy link
Owner

minad commented May 19, 2021

@daviwil Native is cheating ;)

@minad
Copy link
Owner

minad commented May 19, 2021

@jacksonludwig Please give the branch optimize-describe-var a try, see 5b6d95f. Does this improve the situation for you? Generally I am not a fan of such special casing since this will ultimately lead to the accumulation of cruft.

@minad
Copy link
Owner

minad commented May 19, 2021

@daviwil Regarding annotations, these are computed lazily only for the visible candidate and should never make issues with Vertico. Otherwise the UI performance would be horrible.

@daviwil
Copy link
Sponsor

daviwil commented May 19, 2021

Good to know! That's a great way to do it.

@minad
Copy link
Owner

minad commented May 19, 2021

@jacksonludwig I am not sure if I can reproduce this issue.

  1. I create a few variables
(dotimes (i 100000)
  (eval `(defvar ,(intern (format "test-var%s" i)) nil)))
  1. Then I test describe-variable with/without the buffer switching optimization. I enter test var, since this will narrow down such that all the 100k variables stay visible. I don't see a performance difference with the buffer switching "optimization".

Of course there is a noticeable lag, but the UI stays responsive. Vertico already has an optimization which ensures that the running computation is aborted when new input comes in. For me this is still usable even with 100k items.

(while-no-input (vertico--recompute-candidates pt content bounds metadata)))

@hmelman
Copy link

hmelman commented May 19, 2021

FWIW I'm on a 2020 iMac (emacs 27.2) and describe-variable with about 12k candidates using vertico is very responsive.

@jacksonludwig
Copy link
Author

jacksonludwig commented May 19, 2021

@minad
Trying the commit unfortunately does not seem to change anything on my end.

Out of curiosity, are these normal profiler results? This is what I get from calling C-h v.

         569 100% - command-execute
         564  99%  - byte-code
         551  96%   - read-extended-command
         551  96%    - completing-read-default
         551  96%     - apply
         551  96%      - vertico--advice
         551  96%       - #<subr completing-read-default>
         547  96%        - vertico--exhibit
         536  94%         - vertico--update-candidates
         536  94%          - vertico--recompute-candidates
         532  93%           - vertico--all-completions
         532  93%            - completion-all-completions
         532  93%             - apply
         532  93%              - #<subr completion-all-completions>
         532  93%               - completion--nth-completion
         532  93%                - completion--some
         532  93%                 - #<compiled 0x102c25c3c629a8d0>
         532  93%                  - orderless-all-completions
         532  93%                   - orderless-filter
         512  89%                    - help--symbol-completion-table
         512  89%                     - complete-with-action
         508  89%                      - all-completions
         500  87%                         #<compiled 0x759a97608883977>
          20   3%                    - #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_46>
          20   3%                     - complete-with-action
          16   2%                      - all-completions
           4   0%                         #<compiled -0x75fa8d6d42c5d15>
           4   0%           - vertico--sort
           4   0%              #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_5>
          11   1%         - vertico--display-candidates
           6   1%          - vertico--resize-window
           6   1%           - default-line-height
           6   1%              default-font-height
          13   2%   - completing-read
          13   2%    - completing-read-default
          13   2%     - apply
          13   2%      - vertico--advice
          13   2%       - #<subr completing-read-default>
           8   1%        - vertico--exhibit
           4   0%         - vertico--update-candidates
           4   0%          - vertico--recompute-candidates
           4   0%           - vertico--sort
           4   0%              #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_5>
           4   0%         - vertico--format-candidates
           4   0%          - vertico--annotate
           4   0%           - #<compiled -0x1836bb1e1b4be03a>
           4   0%            - mapcar
           4   0%             - #<compiled -0x534ee904ae26085>
           4   0%                marginalia-annotate-variable
           5   0%  - funcall-interactively
           5   0%   - execute-extended-command
           5   0%    - command-execute
           5   0%       funcall-interactively
           0   0% + ...

versus this from calling C-h f

          88 100% - command-execute
          63  71%  - byte-code
          63  71%   - read-extended-command
          63  71%    - completing-read-default
          63  71%     - apply
          63  71%      - vertico--advice
          60  68%       - #<subr completing-read-default>
          42  47%        - vertico--exhibit
          42  47%         - vertico--update-candidates
          42  47%          - vertico--recompute-candidates
          38  43%           - vertico--all-completions
          38  43%            - completion-all-completions
          38  43%             - apply
          38  43%              - #<subr completion-all-completions>
          38  43%               - completion--nth-completion
          38  43%                - completion--some
          38  43%                 - #<compiled -0x68556ca052bc739>
          38  43%                  - orderless-all-completions
          38  43%                   - orderless-filter
          21  23%                    - help--symbol-completion-table
          21  23%                       complete-with-action
          17  19%                    - #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_46>
          17  19%                     - complete-with-action
           8   9%                      - all-completions
           4   4%                         #<compiled -0x75fa8d6d42c5d15>
           4   4%           - vertico--sort
           4   4%              #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_5>
          23  26%  - help-fns--describe-function-or-command-prompt
          23  26%   - completing-read-default
          23  26%    - apply
          23  26%     - vertico--advice
          20  22%      - #<subr completing-read-default>
          16  18%       - vertico--exhibit
          16  18%        - vertico--update-candidates
          16  18%         - vertico--recompute-candidates
          12  13%            vertico--sort
           2   2%  - funcall-interactively
           2   2%   - execute-extended-command
           2   2%      sit-for
           0   0% + ...

And this is with C-h f having almost triple the results as C-h v.

@minad
Copy link
Owner

minad commented May 19, 2021

The second profiler result looks quite normal. Most of the time is spent in the orderless-filter. This is expected. In the first case, most of the time is spent in the all-completions, which calls the predicate. This is what I tried to work around with the commit in 5b6d95f.

Which Emacs version and which os do you use? Can you observe the difference also on emacs -Q, when loading only Vertico and nothing else?

@jacksonludwig
Copy link
Author

jacksonludwig commented May 19, 2021

I use ubuntu and I'm using the latest master with native comp for emacs.

With -Q, I did have less stutter but it was still noticeable after I added dummy variables to match what is usually available with other packages installed (I had around 15k vars).

However, I did see something odd. When running C-h v from a read-only buffer (e.g. splash screen, help buffer, etc) there is no stutter when typing. Running the command in a non-read-only buffer does result in stutter.

To test it, I opened a python file and called C-h m to get a help window up. Then I could tell that while calling C-h v with the help window focused, there was no stuttering whatsoever. Switching to the python buffer and calling C-h v did have the stutter.

@minad
Copy link
Owner

minad commented May 19, 2021

Do you use marginalia-mode? Can you test this with Marginalia disabled? But the profile you collected is odd, and points to the predicate being the issue. Did you also test with 5b6d95f?

@jacksonludwig
Copy link
Author

For me having marginalia enabled/disabled has no difference.

And when I tested with -Q I used the following config:

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package '(vertico :type git :host github :repo "minad/vertico" :commit "5b6d95f316425a533fa14e78732af26b22cf3b22")

@minad minad changed the title Slow when many candidates describe-variable is slow with many variables (Predicate issue?) May 20, 2021
@minad
Copy link
Owner

minad commented May 20, 2021

Okay, I suggest you experiment a bit with the help--symbol-completion-table.

describe-symbol (This is fast for you?):

          (val (completing-read (if found
				    (format
                                     "Describe symbol (default %s): " v-or-f)
				  "Describe symbol: ")
				#'help--symbol-completion-table
				(lambda (vv)
                                  (cl-some (lambda (x) (funcall (nth 1 x) vv))
                                           describe-symbol-backends))
				t nil nil
				(if found (symbol-name v-or-f)))))

describe-variable (This one is slow):

     (setq val (completing-read
                (if (symbolp v)
                    (format
                     "Describe variable (default %s): " v)
                  "Describe variable: ")
                #'help--symbol-completion-table
                (lambda (vv)
                  ;; In case the variable only exists in the buffer
                  ;; the command we switch back to that buffer before
                  ;; we examine the variable.
                  (with-current-buffer orig-buffer                                      ;; Note the buffer switching
                    (or (get vv 'variable-documentation)
                        (and (boundp vv) (not (keywordp vv))))))
                t nil nil
                (if (symbolp v) (symbol-name v))))

The difference is in the predicate. You can copy the describe-variable source to a separate buffer, then replace the predicate with the following code, then compile-defun the modified definition to check if the buffer switching is the issue:

                (lambda (vv)
                    (or (get vv 'variable-documentation)
                        (and (boundp vv) (not (keywordp vv)))))

@jacksonludwig
Copy link
Author

jacksonludwig commented May 20, 2021

@minad
Yep, when switching out the predicate, the slowness is gone.

@minad minad changed the title describe-variable is slow with many variables (Predicate issue?) describe-variable is slow with many variables due to buffer switching in predicate May 20, 2021
@oantolin
Copy link

The slower predicate is also correct. I mean, I'd expect describe-variable to mean "describe variables in the current buffer". I think this is a case where I'd trade correctness for speed though.

@minad
Copy link
Owner

minad commented May 22, 2021

Of course it should be correct. I am often using describe-variable+Marginalia to check some buffer values without actually executing the command.

One could add some special casing. For example "normalize" the completion table early on using all-completions.

@minad
Copy link
Owner

minad commented May 30, 2021

http://debbugs.gnu.org/cgi/bugreport.cgi?bug=48738

@minad
Copy link
Owner

minad commented May 31, 2021

Fixed in Emacs 28, closing here.

@minad minad closed this as completed May 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants