emacs-which-key
emacs-which-key copied to clipboard
Lots of time spent inside which-key--maybe-replace
I think this is a which-key issue and not specifically related to spacemacs as it happens in multiple different major modes (ruby and org).
I found this but it is not related as I am on the most recent spacemacs develop and I have tried with the most recent which-key as well.
Output from profiler:
- timer-event-handler 4120 74%
- apply 4119 74%
- which-key--update 4068 73%
- which-key--create-buffer-and-show 4068 73%
- which-key--get-bindings 3761 68%
- which-key--format-and-replace 3713 67%
- which-key--maybe-replace 3658 66%
which-key--match-replacement 50 0%
+ which-key--get-pseudo-binding 35 0%
+ which-key--propertize-description 17 0%
+ kbd 14 0%
+ which-key--propertize 1 0%
which-key--extract-key 1 0%
which-key--maybe-add-docstring 1 0%
+ which-key--propertize-key 1 0%
+ which-key--get-current-bindings 45 0%
+ sort 1 0%
+ which-key--show-page 173 3%
+ which-key--create-pages 133 2%
+ sp-show--pair-function 11 0%
Description :octocat:
emacs gets really slugging when hitting a key that triggers which-key and it takes several seconds for the menu to show during which time emacs is maxing out a core.
Reproduction guide :beetle:
- Start Emacs
- Fiddle around in either org mode or ruby (might be others too)
Observed behaviour: :eyes: :broken_heart: emacs gets really slugging when hitting a key that triggers which-key and it takes several seconds for the menu to show during which time emacs is maxing out a core.
Expected behaviour: :heart: :smile: which-key to react instantly
System Info :computer:
- OS: gnu/linux
- Emacs: 26.3
- Spacemacs: 0.300.0
- Spacemacs branch: peter_develop (rev. 8916788d5)
- Graphic display: t
- Distribution: spacemacs
- Editing style: vim
- Completion: ivy
- Layers:
((auto-completion :variables auto-complete-idle-delay 0.1 auto-completion-complete-with-key-sequence "jk" auto-completion-enable-snippets-in-popup t auto-completion-enable-help-tooltip t auto-completion-return-key-behavior nil auto-completion-tab-key-behavior 'cycle)
better-defaults
(dash :variables dash-docs-docset-newpath
(cond
((spacemacs/system-is-linux)
(expand-file-name "Zeal/Zeal/docsets"
(getenv "XDG_DATA_HOME")))
((spacemacs/system-is-mac)
(expand-file-name "Library/ApplicationSupport/Dash/DocSets"
(getenv "HOME")))))
evil-commentary
(finance :variables ledger-use-iso-dates t)
(geolocation :variables geolocation-enable-automatic-theme-changer nil geolocation-enable-location-server t geolocation-enable-weather-forecast t)
graphviz ivy multiple-cursors
(mu4e :variables mu4e-account-alist nil mu4e-mu-home
(expand-file-name "mu"
(getenv "XDG_CACHE_HOME"))
mu4e-maildir
(expand-file-name "mail"
(getenv "XDG_CACHE_HOME"))
mu4e-enable-async-operations nil mu4e-enable-notifications t mu4e-use-maildirs-extension nil)
pandoc pdf plantuml
(shell :variables shell-default-height 30 shell-default-position 'bottom shell-default-shell 'vterm shell-scripts-backend 'lsp)
(templates :variables templates-use-default-templates t)
theming treemacs
(git :variables git-magit-status-fullscreen t)
pass
(spell-checking :variables enable-flyspell-auto-completion nil flyspell-issue-welcome-flag nil ispell-local-dictionary "en" ispell-program-name
(or
(getenv "ASPELL")
"/run/current-system/sw/bin/aspell")
ispell-really-aspell t spell-checking-enable-by-default
(spacemacs/system-is-linux)
spell-checking-enable-auto-dictionary nil)
version-control ansible nginx terraform yaml c-c++ cmake
(crystal :variables crystal-enable-auto-format t)
dap emacs-lisp
(haskell :variables flycheck-haskell-runghc-command
'("stack" "--verbosity" "silent" "runghc" "--no-ghc-package-path" "--" "--ghc-arg=-i")
haskell-completion-backend 'lsp haskell-enable-hindent t haskell-process-type 'stack-ghci haskell-stylish-on-save t)
(lsp :variables lsp-auto-guess-root t lsp-navigation 'both lsp-ui-doc-use-webkit nil lsp-ui-flycheck-enable t lsp-ui-sideline-enable t)
nixos
(python :variables python-enable-yapf-format-on-save t)
(ruby :variables ruby-backend 'lsp ruby-enable-enh-ruby-mode nil ruby-highlight-debugger-keywords t ruby-test-runner 'rspec ruby-version-manager nil)
shell-scripts
(sql :variables sql-auto-indent t sql-capitalize-keywords t)
typescript
(windows-scripts :variables dos-prefer-bat-mode nil)
ruby-on-rails gtags
(restclient :variables restclient-use-org nil)
syntax-checking csv html
(markdown :variables markdown-live-preview-engine 'eww)
(org :variables org-enable-hugo-support t org-enable-jira-support t org-enable-reveal-js-support t org-enable-trello-support
(spacemacs/system-is-linux)
org-export-backends
'(ascii html icalendar latex md)
org-reveal-note-key-char nil org-use-speed-commands nil)
(speartail :variables speartail-auto-save-interval nil)
nix peter)
- System configuration features: XPM JPEG TIFF GIF PNG RSVG IMAGEMAGICK SOUND DBUS GSETTINGS GLIB NOTIFY LIBSELINUX GNUTLS LIBXML2 FREETYPE M17N_FLT LIBOTF XFT ZLIB TOOLKIT_SCROLL_BARS GTK3 X11 XDBE XIM MODULES THREADS XWIDGETS LIBSYSTEMD LCMS2
Just cloned spacemacs and I'm not seeing any slow down. Can you try to narrow it down more?
I have some fairly long running emacs sessions and I cannot easily reproduce it at the beginning of a session as it takes hours most of the time to trigger. I will try with a cut down config to see if I can more easily reproduce it.
As an additional data point, this is the profiler after a restart of emacs when things are responding properly:
- timer-event-handler 1503 58%
- apply 1503 58%
- which-key--update 1371 52%
- which-key--create-buffer-and-show 1369 52%
- which-key--show-page 644 24%
- which-key--show-popup 640 24%
- which-key--show-buffer-side-window 640 24%
- display-buffer-in-side-window 557 21%
- window--make-major-side-window 557 21%
- window--display-buffer 536 20%
- which-key--fit-buffer-to-window-horizontally 281 10%
- apply 281 10%
- fit-window-to-buffer 281 10%
- apply 281 10%
- doom-modeline-redisplay 259 10%
- redisplay 256 9%
+ redisplay_internal (C function) 23 0%
+ timer-event-handler 10 0%
#<compiled 0x1b28ef> 22 0%
- #<lambda 0x15426159d> 248 9%
- fit-window-to-buffer 248 9%
- apply 248 9%
- doom-modeline-redisplay 219 8%
- redisplay 219 8%
+ redisplay_internal (C function) 10 0%
+ timer-event-handler 3 0%
+ #<compiled 0x1b28ef> 29 1%
+ set-window-buffer 7 0%
+ split-window-no-error 21 0%
+ display-buffer-reuse-window 83 3%
+ which-key--process-page 3 0%
- which-key--get-bindings 615 23%
- which-key--format-and-replace 500 19%
- which-key--maybe-replace 396 15%
which-key--match-replacement 102 3%
+ which-key--get-pseudo-binding 35 1%
+ which-key--propertize-description 57 2%
+ kbd 17 0%
+ which-key--maybe-add-docstring 11 0%
+ which-key--extract-key 2 0%
+ which-key--get-current-bindings 98 3%
+ sort 6 0%
+ which-key--create-pages 109 4%
+ which-key--start-timer 1 0%
+ sp-show--pair-function 67 2%
+ org-indent-initialize-agent 57 2%
+ auto-revert-buffers 3 0%
+ #<compiled 0x2024d3> 2 0%
A few more observations:
- It doesn't seem to be related to the issue mention above as
(length which-key-replacement-alist)
hasn't gone above 285. - Sometimes the problem goes away without restarting emacs but then it comes back again later.
Thanks. If it’s an issue with how the replacement stuff is working it could very well depend on what major mode you’re in.
So far I have seen it in ruby-mode (the built-in, I haven't tried enh-ruby-mode) as well as org-mode.
Never in elisp.
I think that the profiler output is a red herring. The issue isn't actually about which-key but has to do with lsp.
I disabled the lsp layer (spacemacs) and I have not been able to reproduce it since in any of the otherwise affected modes but the profile still says that a significant amount of cpu time is spent in which-key (not quite as much though), so I'm guessing that which-key just happens to be using the most cpu but that the slow-downs is due to emacs being busy with lsp.
Ah ok. If it’s just counting function calls it makes sense that there are quite a few in which-key.
I'm seing the same issue when enbaling lsp module in doom emacs
@pcharest2000 - do you mind trying to run a profiler session to see if you're seeing lots of which-key
activity as well?
Peter here is the profiler output yes lots of which-key and this is a session that slow down was sporadic:
- timer-event-handler 9419 71%
- apply 9419 71%
- which-key--update 8025 60%
- which-key--create-buffer-and-show 8025 60%
- which-key--get-bindings 7157 54%
- which-key--format-and-replace 7078 53%
- which-key--maybe-replace 6725 51% which-key--match-replacement 349 2%
- which-key--get-pseudo-binding 128 0%
- replace-regexp-in-string 11 0%
- which-key--propertize-description 150 1%
- kbd 42 0%
- which-key--maybe-add-docstring 25 0%
- which-key--extract-key 11 0% which-key--propertize-key 4 0%
- which-key--get-current-bindings 54 0%
- sort 14 0% + which-key--create-pages 861 6% + which-key--show-page 3 0%
- lsp--on-idle 816 6%
- company-idle-begin 360 2%
- #<compiled 0xcd0681> 102 0%
- lsp-ui-sideline--run 102 0%
- #<compiled 0x20306b> 20 0%
- #<compiled 0x1814099> 6 0% jit-lock-force-redisplay 4 0% amx-idle-update 3 0%
- #<compiled 0x1511521> 2 0%
- command-execute 2396 18%
- redisplay_internal (C function) 556 4%
- #<compiled 0x248545d> 361 2%
- ... 304 2%
- lsp--post-command 34 0%
- evil--jump-hook 20 0%
@pcharest2000 - do you mind trying to run a profiler session to see if you're seeing lots of
which-key
activity as well?
I've reconfigured my doom emacs to use irony-server instead of LSP (cc) and here are the results of the profiler, LSP is bogging down something:
- command-execute 3311 47%
- timer-event-handler 2389 34%
- apply 2387 34%
- which-key--update 2015 29%
- amx-idle-update 174 2%
- company-idle-begin 107 1%
- #<compiled 0x20306b> 8 0% company-echo-show 4 0%
- ... 604 8%
If you suspect which-key--update
is the culprit you can lengthen which-key-idle-delay
then toggle which-key-mode
and see if that has any effect. If you have a short delay, that function will fire a lot (but exit quickly most of the time). Or even more severely, just disable which-key-mode
when you notice the slowdown and see if it goes away.
The profiler output seems pretty consistent with respect to which-key, so if you are getting slow down in certain files, it's unlikely to be which-key's fault. Having a mode that calls out to external processes, like LSP, seems like the much more likely culprit.
Seting which-key-idle-delay to 0.5 and the secobndary delay to 0.1 seems to resolve the issue. But I still don't understund how this related to LSP mode. Please I am an emacs user not an expert how things works in emacs.
TKS I'll keep you posted
The culprit is definitely which-key
.
I enabled lsp
(with spacemacs) and used emacs as normal. After a while, the problem came back and disabling which-key-mode
made everything snappy again.
And not just that - I realized that regardless of lsp, which-key-mode
causes noticeable lag compared to no which-key
where responses are simply instantaneous. I just didn't notice this before as which-key
was always enabled. Increasing which-key-idle-delay
to 0.5
seems to have made things acceptable again.
I don't use lsp and I'm afraid there's not enough information here for me to track down a problem. Performance issues with which-key are rare in my experience, but that doesn't mean there isn't one here.
One important issue here is that there are many possible sources of problems here, which-key, lsp, the major mode used, spacemacs config for lsp, etc. and I don't have the time to test out each one. If someone can create a kind of minimal working example from an otherwise unconfigured emacs, I can try to make progress on this.
I stumbled across this because I was having the same problems, which the same profiler behavior. Thanks for the advice. I set these two variables back to which-key defaults:
(setq which-key-idle-delay 1.0)
(setq which-key-idle-secondary-delay nil)
Emacs instantly became snappier, even without a restart.
I stumbled across this because I was having the same problems, which the same profiler behavior. Thanks for the advice. I set these two variables back to which-key defaults:
(setq which-key-idle-delay 1.0) (setq which-key-idle-secondary-delay nil)
Emacs instantly became snappier, even without a restart.
This doesn't work for me. In my case, which-key causes hanging only when it's triggered after 'SPC' leader key. I followed suggestions from https://github.com/justbur/emacs-which-key, which make Emacs work with no hanging.
(setq which-key-idle-delay 10000) (setq which-key-idle-secondary-delay 0.05)
I stumbled across this because I was having the same problems, which the same profiler behavior. Thanks for the advice. I set these two variables back to which-key defaults:
(setq which-key-idle-delay 1.0) (setq which-key-idle-secondary-delay nil)
Emacs instantly became snappier, even without a restart.
This doesn't work for me. In my case, which-key causes hanging only when it's triggered after 'SPC' leader key. I followed suggestions from https://github.com/justbur/emacs-which-key, which make Emacs work with no hanging.
(setq which-key-idle-delay 10000) (setq which-key-idle-secondary-delay 0.05)
This solution seems to work for me. Is there any drawback or potential issue?
My earlier post did make things faster, but only partially, for a little while. This latter version:
(setq which-key-idle-delay 10000)
(setq which-key-idle-secondary-delay 0.05)
works great. In fact, this makes the difference in making Spacemacs as fast for me as my own vanilla emacs with similar packages, which I've never been able to achieve before.
The key downside is that the whick-key menus don't show anymore, so it essentially disables which-key. So I've added the option:
(setq which-key-show-early-on-C-h t)
so that I can still see the which-key menus when I want by hitting C-h. The drawback of the above is that which-key isn't as accessible as it was, but the speed bump is definitively worth it.
@dmusican aren't you experiencing problems with the ESC
key? I mean, my main problem with this configuration now is that often I can't exit insert mode by just pressing ESC
, but I have to resort to M-x evil-normal-state
. Is it just a problem of mine?
@tigerjack I'm using holy-mode, not evil-mode, so no, I'm not seeing that...
I have no interest in debugging this problem within spacemacs. In order to make progress, I'm going to need some way of reproducing the issue from a simple configuration on top of vanilla emacs.
It's true that a lower value for which-key-idle-delay
will cause which-key to "check in" more often and consume more resources, but outside of this issue the resource consumption has never been a problem.
I don't know whether this data point is helpful, but when I trigger garbage collection manually with garbage-collect, the problem disappears for a while.
In a session that has been running for a long time, manually triggering a garbage collection also makes it faster for me, for a short time. After running garbage-collect, if I follow the procedure:
- Type SPC
- Wait for which-key to appear
- Type C-g
- Repeat
Step 2 noticeably takes a little bit longer each time.
If I restart Emacs, everything is snappy regardless of whether I run garbage-collect, and it doesn't noticeably slow down with repetition.
I've ran into this problem. The issue is the dolist
that ends up doing around 15k iterations per key stroke for me. @hlissner suggested a fix like this before:
(defun which-key--maybe-replace (key-binding &optional prefix)
(let* ((pseudo-binding (which-key--get-pseudo-binding key-binding prefix))
one-match)
(if pseudo-binding
pseudo-binding
(catch 'result
(let* ((all-repls
(append (cdr-safe (assq major-mode which-key-replacement-alist))
which-key-replacement-alist)))
(dolist (repl all-repls)
(when (and (or which-key-allow-multiple-replacements
(not one-match))
(which-key--match-replacement key-binding repl))
(setq one-match t
key-binding
(cond ((or (not (consp repl)) (null (cdr repl)))
key-binding)
((functionp (cdr repl))
(funcall (cdr repl) key-binding))
((consp (cdr repl))
(cons
(cond ((and (caar repl) (cadr repl))
(replace-regexp-in-string
(caar repl) (cadr repl) (car key-binding) t))
((cadr repl) (cadr repl))
(t (car key-binding)))
(cond ((and (cdar repl) (cddr repl))
(replace-regexp-in-string
(cdar repl) (cddr repl) (cdr key-binding) t))
((cddr repl) (cddr repl))
(t (cdr key-binding)))))))
(unless which-key-allow-multiple-replacements
(throw 'result key-binding)))))
key-binding))))
does this help speed things up for anyone?
@rgrinberg It's possible that that is a source of slow down. I have thought about speeding up that code in the past but it didn't seem to be an issue for anyone, including me. If this is indeed the source of the problem, I would consider a pr.
I put up a PR in #255 @peterhoeg and other posters who experienced a slow down, would you mind trying out my patch?
I tried it out, and this seems to be a notable improvement. Thanks!
I still have this issue even after #255 . My profile is more specific, the looping is located in
which-key--replace-in-repl-list-many
:
- timer-event-handler 17837 93%
- apply 17837 93%
- which-key--update 17634 92%
- which-key--create-buffer-and-show 17634 92%
- which-key--get-bindings 17154 90%
- which-key--format-and-replace 16898 88%
- which-key--maybe-replace 16728 88%
- apply 16648 87%
- which-key--replace-in-repl-list-many 16598 87%
which-key--match-replacement 210 1%
- My
which-key-replacement-alist
is 140K in size. -
garbage-collect
has no effect - with restart it is performant again, even though
which-key-replacement-alist
is still big at 100K. However, the above profile still is taking 44% of the CPU. Better than 93% but still a pretty high number.
Do you really need multiple replacements? I would suggest that you disable them as they're the main source of the slow down and are much trickier to optimize.
I had no idea this was an option, checking ...
ADD: okay I disabled it. Let me use it for a while and see how it goes.
I'm not sure everyone is aware of the automatic string replacement option, described here in the README. Any time it's used it subverts the "generic" replacement mechanism and should be much faster. There are some things you can't do with it, but it should cover most use cases. It also has the conceptual advantage of making it so your description for the command is defined simultaneously with the keybinding itself, making it easy to keep both together in your configuration.
Now which-key--replace-in-repl-list-once
is the culprit. It is not resolved.
@justbur I am trying that out now.
@justbur
Any time it's used it subverts the "generic" replacement mechanism and should be much faster.
I am suffering from this too (in spacemacs develop branch with evil-mode).. Is there a specific reason, why which-key has to compute the replacements on every invocation? could the replacements be precomputed and stored in the same way the "automatic replacements" are stored internally? meaning: could the results of the replacements - or even the which-key output be cached?
i have disabled multiple replacements for now and am still testing it..
I suppose we need to refactor this feature to make it even faster. Justin has an idea on how to do this here. I can take a stab at it if we agree that this is what we should do.
Yeah, even setting which-key-enable-extended-define-key t
doesn't help. I was wondering what was slow about Spacemacs that was motivating people to switch to Doom. Maybe this is a factor, except I think Doom uses which-key also.
Maybe this is a factor, except I think Doom uses which-key also.
I use doom and we have the same problem ;) On Aug 18, 2020, 2:18 PM -0700, vv111y [email protected], wrote:
Yeah, even setting which-key-enable-extended-define-key t doesn't help. I was wondering what was slow about Spacemacs that was motivating people to switch to Doom. Maybe this is a factor, except I think Doom uses which-key also. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
Huh, go figure. I wonder if we can also make it async (on top of hash tables) for those key-chords that are in muscle memory, but yet have it enabled for those less used features you can't quite remember.
There’s no need to make it async. Just doing a constant time instead of a linear lookup should be more than enough to make this operation instantaneous. On Aug 18, 2020, 2:28 PM -0700, vv111y [email protected], wrote:
Huh, go figure. I wonder if we can also make it async (on top of hash tables) for those key-chords that are in muscle memory, but yet have it enabled for those less used features you can't quite remember. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
Sounds good, I'm looking through the source myself fwiw. Anything I can do to help?
i have disabled multiple replacements for now and am still testing it..
i can confirm that disabling multiple repacements does not resolve the issue..
I am suffering from this too (in spacemacs develop branch with evil-mode).. Is there a specific reason, why which-key has to compute the replacements on every invocation? could the replacements be precomputed and stored in the same way the "automatic replacements" are stored internally? meaning: could the results of the replacements - or even the which-key output be cached?
For years there were no complaints, so I left it as is. The mechanism can be changed, but the features would likely have to change too. Technically, replacements can depend on state (like which minor modes are active) and this makes caching difficult. Simplifying what's possible with replacements would make caching a possibility and would speed things up. Just recognize that the problem is not trivial, because in emacs which keys are available when can be complicated. Again, not saying there's not room for improvement here.
Yeah, even setting which-key-enable-extended-define-key t doesn't help.
Doing that by itself does nothing. What that setting does is to allow for you to specify replacements withing define-key
itself. These replacements are then specific to the keymap and are actually stored in the keymap itself. This makes looking up these replacements extremely fast. That setting has no effect on replacements specified using which-key-replacement-alist
and friends. They are two distinct mechanisms.
I also can't help but wonder how it could be the case that which-key-replacement-alist
got so large for some people. I think I have maybe 10 rules in there or something like that.
fwiw I have 682 packages installed. Maybe that effects it
fwiw I have 682 packages installed. Maybe that effects it
It's more of an issue if you have lots of layers (spacemacs) or modules (doom) enabled. Every layer/module adds keybindings and modified the which-key-replacement-alist
to prettify the display. This does not scale well.
@justbur Thanks for the clarification.
Looks like the replacement feature was not intended to be used with 100s of kilobytes of replacement data. which is not likely to happen when building the list by hand for ones individual config. Now, distributions like spacemacs rely heavily on which-key but seem to misuse the replacements by loading/defining too many rules (for the current implementation). am i understanding this correctly?
making this work in constant time would be great. If that is too much effort for now, maybe the problem could be avoided/mitigated by encouraging developers to use the extended define whenever possible. you could issue a warning message to use extended-define when the replacement list gets above 20k or so.
fwiw I have 682 packages installed. Maybe that effects it
It's more of an issue if you have lots of layers (spacemacs) or modules (doom) enabled. Every layer/module adds keybindings and modified the
which-key-replacement-alist
to prettify the display. This does not scale well.
I also happen to have the issue on my spacemacs distribution. I tested with only a handful of layers activated (just helm
and ibuffer
). I think I had 20k of data in which-key-replacement-alist
.
Setting which-key-idle-delay
to a very high value seems to fix the issue for now. I've read all comments and that would be the only workaround if I understand correctly?