evil
evil copied to clipboard
line-move-1 is sometimes slow
Question
move-line-1 is sometimes slow for me, causing perceived scrolling performance to suffer. Performance seems sensitive to a number of factors I can't seem to pin down. Certain lines are worse, especially long ones. Certain modes are worse, especially clojure-mode. Here is the CPU profiler output:
- command-execute 88 80%
- call-interactively 88 80%
- funcall-interactively 88 80%
- evil-previous-line 55 50%
- evil-line-move 55 50%
- previous-line 55 50%
- line-move 54 49%
line-move-1 54 49%
called-interactively-p 1 0%
- evil-next-line 29 26%
- evil-line-move 29 26%
- next-line 29 26%
- line-move 29 26%
- line-move-1 28 25%
- #<compiled 0x15613263da59> 2 1%
I set gc-cons-threshold to max value for this test. The test was simply to scroll up and down for a few seconds with k and j. Is there any mitigations that can be done to speed this function up?
Thanks, John C.
Environment
Emacs version: 28.0.50
Operating System: Uubntu 20.04 with Wayland
Evil version: 1.14.0
Evil installation type: Doom Emacs
Graphical/Terminal: Graphical (daemon)
Tested in a make emacs session (see CONTRIBUTING.md): No
Reproduction steps
- NA
Expected behavior
It's super fast
Actual behavior
It's noticeably sow
line-move-1 isn't part of evil, and I personally have never experienced any slowdown with these functions. I would suspect the issue is somewhere in your config.
@jgkamat I've experienced the same issue. it's true that line-move-1 isn't part of evil. However, it looks to me like evil uses it differently than vanilla emacs. To see this try profiling scrolling up and down with emacs file and emacs -Q file. In my case, I observed that vanilla emacs was not calling line-move-1.
In my case, with vanilla emacs
- next-line 70 28%
- line-move 66 26%
line-move-visual 26 10%
- line-move-partial 9 3%
- window-inside-pixel-edges 3 1%
- window-edges 2 0%
window-normalize-window 1 0%
- default-line-height 3 1%
default-font-height 3 1%
- window-screen-lines 1 0%
window-inside-pixel-edges 1 0%
- window-inside-pixel-edges 4 1%
- window-edges 4 1%
window-normalize-window 1 0%
window-current-scroll-bars 1 0%
and with evil inside doom-emacs
- evil-next-line 28 20%
- let 28 20%
- evil-line-move 28 20%
- cond 28 20%
- let 28 20%
- condition-case 28 20%
- progn 28 20%
- let 28 20%
- condition-case 28 20%
- with-no-warnings 28 20%
- funcall 28 20%
- next-line 28 20%
- line-move 28 20%
- line-move-1 28 20%
- vertical-motion 1 0%
If we take a look at simple.el we see
(defun line-move (arg &optional noerror _to-end try-vscroll)
"Move forward ARG lines.
If NOERROR, don't signal an error if we can't move ARG lines.
TO-END is unused.
TRY-VSCROLL controls whether to vscroll tall lines: if either
`auto-window-vscroll' or TRY-VSCROLL is nil, this function will
not vscroll."
(if noninteractive
(line-move-1 arg noerror)
(unless (and auto-window-vscroll try-vscroll
...
However, unlike what you might expect from line-move-1's description, it looks to me like line-move (and line-move-visual/line-move-partial) do not just do some preprocessing & then call line-move-1. Instead, it looks like it tries to just set the position of the point manually, falling back on line-move-1 only in some cases. Interestingly, the comment that line-move-1 is the "guts" of next-line is circa 1995, whereas a lot of other editing has gone on since then.
If line-move-visual is used by upstream emacs in preference to line-move-1, you might expect that it had been the target for more optimization than line-move-1. And it's certainly had a lot more changes. line-move-1 hasn't been touched since about 2008, whereas line-move-visual had changes is 2018. And of course there might have been changes to the C code and elisp runtime which were evaluated in terms of how they performed on line-move-visual. Interestingly, evil-line-move was written in 2011-2012, with a note to the effect that the author hopes it will continue to work properly.
Anyway, evil-line-move appears to basically work by funcalling next-line, which presumably means it's considered non-interactive and so we go straight to line-move-1. Maybe if you modified it to use line-move-visual instead, you'd get better performance. Since you presumably still want it to run the next-line code, you'd probably have to change the conditional at the top of line-move to (if (and noninteractive some-control-var-which-defaults-to-true)
dmr writes:
Anyway,
evil-line-moveappears to basically work by funcallingnext-line, which presumably means it's considered non-interactive and so we go straight toline-move-1.
I don't think this is true, I'm fairly sure noninteractive' is only true in batch mode. However, the end result is true, as evil-next-line' sets
line-move-visual to nil to mirror the behavior of vim (I think).
Maybe if you modified it to use
line-move-visualinstead, you'd get better performance.
This would change the behavior of the function as well. That said, do you see better performance with line-move-visual?
To see this try profiling scrolling up and down with
emacs fileandemacs -Q file. In my case, I observed that vanilla emacs was not callingline-move-1.
It would help if I could have the file or anything else to go on - I still can't get any meaningful slowdown on this function, for me the M-x call dominates the profile, and I don't get very many samples inside these functions. In addition, could you try running evil by itself (with no other config).
@jgkamat I do see substantially better performance with emacs -Q than doom-emacs, although even though I've taken out a lot of the default stuff, that could still be due to a lot of things.
If I take out almost everything, so that the init file looks like this (this is also the file i'm scrolling around btw), the performance appears to be somewhat worse, but probably not enough to worry about
- command-execute 31 52%
- call-interactively 31 52%
- funcall-interactively 31 52%
+ evil-previous-line 23 38%
+ evil-next-line 7 11%
+ doom/toggle-profiler 1 1%
+ redisplay_internal (C function) 9 15%
+ gcmh-register-idle-gc 4 6%
- command-execute 50 62%
- call-interactively 49 61%
- funcall-interactively 49 61%
+ next-line 24 30%
+ previous-line 23 28%
+ eval-last-sexp 1 1%
- ... 23 28%
Automatic GC 23 28%
+ redisplay_internal (C function) 5 6%
+ undo-auto--add-boundary 1 1%
Not really sure what to say about this. However:
- command-execute 5,136,879 89%
- call-interactively 5,136,879 89%
- funcall-interactively 5,136,879 89%
+ doom/toggle-profiler 3,793,255 66%
+ evil-previous-line 1,051,896 18%
+ evil-next-line 291,728 5%
+ redisplay_internal (C function) 284,860 4%
+ evil-normal-post-command 102,568 1%
- command-execute 4,283,115 80%
- call-interactively 4,270,443 80%
- funcall-interactively 4,270,443 80%
+ eval-last-sexp 3,806,830 71%
+ next-line 301,373 5%
+ previous-line 162,240 3%
+ redisplay_internal (C function) 1,023,141 19%
we appear to do a lot more consing in the evil version.
dmr writes:
If I take out almost everything, so that the init file looks like this (this is also the file i'm scrolling around btw), the performance appears to be somewhat worse, but probably not enough to worry about
Could you try without any config, just emacs and evil? I want to rule out the possibility that the slowdown could be triggered by doom, as the two people who reported this both use doom.
That file seems fine to me when scrolling. This is what I see (plain emacs with evil, scrolling on the linked init file):
- command-execute 45 64%
- call-interactively 45 64%
- funcall-interactively 28 40%
- execute-extended-command 14 20%
- evil-next-line 11 15%
- evil-previous-line 3 4%
which is basically just noise
I'm also confused what the difference is between the top two and bottom two profiles you pasted - the bottom two seem much more active.
the profiles are
- doom cpu
- plain cpu
- doom mem
- plain mem
@cartesian-theatrics are you using the pgtk branch of Emacs (looks like you are on Wayland with 28.0.50...) by any chance?
I am experiencing the exact same issue: moving line up or down is smooth / fast, however, after a while, it becomes extremely slow without anything obvious when profiling. My assumption is that it is a regression related to pgtk. When I use Emacs in a terminal (emacs -nw) or Emacs 28.0.50 master (not pgtk) the sluggishness does not occur, even after extended use.
Hi, below is a video demonstrates the problem. You can see the keypress on the right side.
In the video you can see the lag when I call evil-next-line or next-line by pressing j down in evil-normal-state. There’s no animation of the cursor go down each line. The cursor went to the destination after I release the key.
And the cursor moving is pretty smooth when I call next-line with C-n in evil-insert-state.
emacs version 28.0.50 https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=dd34bef7d3769a8574bcee2c1e91e8445129af75 evil version b237462feed177cc74a48f462bc9356681a60ff8 system macos 11.4
video url: https://youtu.be/mKQLBNvDNHk
BTW:
- there's no lag when using emacs in terminal emulator like alacritty or kitty
- there's no lag calling
evil-next-lineinevil-insert-state
Hi, below is a video demonstrates the problem. You can see the keypress on the right side.
In the video you can see the lag when I call
evil-next-lineornext-lineby pressingjdown inevil-normal-state. There’s no animation of the cursor go down each line. The cursor went to the destination after I release the key.And the cursor moving is pretty smooth when I call
next-linewithC-ninevil-insert-state.emacs version 28.0.50 git.savannah.gnu.org/cgit/emacs.git/commit/?id=dd34bef7d3769a8574bcee2c1e91e8445129af75 evil version b237462 system macos 11.4
video url: youtu.be/mKQLBNvDNHk
BTW:
- there's no lag when using emacs in terminal emulator like alacritty or kitty
- there's no lag calling
evil-next-lineinevil-insert-state
never mind, test again, found it's the problem of the key (C-n or j) I'm pressing, nothing to do with evil
I am closing this since specially after some recent simplifications evil-next-line/evil-previous-line are just calls to line-move, with line-move-visual set to nil to match Vim. Instead, if the issue persists, please ask on the Doom Emacs or GNU Emacs issue trackers as they are more likely to know the cause of the problem.
In case anyone faces the same issue. My issue was caused by a macOS input method.
Details here https://www.reddit.com/r/emacs/comments/otpruw/rendering_lag_when_press_single_key_in_emacs_gui/h6y5m7r/ The full post is here https://www.reddit.com/r/emacs/comments/otpruw/rendering_lag_when_press_single_key_in_emacs_gui/