evil icon indicating copy to clipboard operation
evil copied to clipboard

line-move-1 is sometimes slow

Open cartesian-theatrics opened this issue 5 years ago • 9 comments
trafficstars

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

cartesian-theatrics avatar May 11 '20 23:05 cartesian-theatrics

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 avatar Oct 01 '20 05:10 jgkamat

@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)

dradetsky avatar Jan 31 '21 01:01 dradetsky

dmr writes:

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.

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-visual instead, 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 file and emacs -Q file. In my case, I observed that vanilla emacs was not calling line-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 avatar Jan 31 '21 03:01 jgkamat

@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.

dradetsky avatar Jan 31 '21 03:01 dradetsky

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.

jgkamat avatar Jan 31 '21 04:01 jgkamat

the profiles are

  • doom cpu
  • plain cpu
  • doom mem
  • plain mem

dradetsky avatar Jan 31 '21 08:01 dradetsky

@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.

mpsq avatar Jul 15 '21 16:07 mpsq

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-line in evil-insert-state

yqrashawn avatar Jul 28 '21 06:07 yqrashawn

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 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-line in evil-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

yqrashawn avatar Jul 29 '21 03:07 yqrashawn

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.

axelf4 avatar Jan 09 '23 10:01 axelf4

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/

yqrashawn avatar Jan 09 '23 11:01 yqrashawn