Subject: Re: bug#78766: 100-4000x redisplay slowdown with vscroll>0 and make-cursor-line-fully-visible=t

From: JD Smith <[email-masked]>
Date: Thu, 12 Jun 2025 14:00:17 -0400

> On Jun 12, 2025, at 2:01 AM, Eli Zaretskii <[email-masked]> wrote:

Thanks for taking a look.

> AFAIU, you are asking Emacs to do the impossible: make the cursor line
> fully-visible when the cursor line is at the top of the window, and
> therefore _cannot_ be fully visible due to non-zero vscroll.  Am I
> right, or did I miss something?

> If I'm right, then could you please explain what do you expect in this
> situation, and why do you expect this to do anything useful?

Other than the extreme slowness, the current behavior with make-cursor-line-fully-visible=t is fine.  Commands which move window-start check for partially visible cursor lines (at the bottom of the window) and make them fully visible, presumably by zeroing vscroll and changing window-start.  Other point movement commands do nothing special.  I do note that line-move as a special case usually resets vscroll to zero, which quickly relieves the issue.

> I haven't yet step through the code in this case, but my guess is that
> the display engine attempts all kinds of measures to make the cursor
> line visible, eventually failing, and those attempts are expensive
> because the display code was never designed to cope with such a
> strange set of conflicting requirements.  In particular, it expects
> the partially-visible line situation to happen at the bottom of the
> window, not at its top.

Interesting.   If there's not a straightforward way to avoid these expensive (and doomed to fail) attempts, perhaps a means of preventing the cursor from landing on a partially visible top line would be a better approach.  Or even forcibly disabling the checks when point is on the first line of the window, and updating the docs to mention `make-cursor-line-fully-visible=t' works only on the final line.

> We could perhaps improve
> the situation in some way, but I need to understand why such a strange
> combination of display-related knobs is used in the first place, and
> what do you expect it to produce and why.

This reproduction is not of course meant to be a realistic code path, it's merely a straightforward way to reliably demonstrate the issue.  The situation of a cursor landing on a partially visible top line can happen naturally whenever vscroll>0 in a window.  Non-zero vscroll is used by pixel scrolling, but also by line-move with tall lines (e.g. images).  

Point ends up on a partial first line for a variety of reasons.  For example, it happens reliably when folding (hiding) an org-mode block which occupies the full window height in a separate window on the frame.  Check the ultra-scroll issue[1] for a video of this.  

I suspect more people haven't noticed this because the #1 way to get non-zero vscroll is pixel-scrolling, and pixel-scroll-precision-mode disables the fully-visible cursor line checks which are apparently misbehaving.

>> I've profiled the slow case, see attached for the important parts.  As is clear, of the ~8s it took to move to the end of the line (twice), get_next_display_element and set_iterator_to_next are the main culprits (arrived at separately via try_window and partial_line_height) with gui_produce_glyphs contributing. 
> 
> These are all normally called functions, so the profile doesn't help
> much.  My guess is that some of the functions are called an abnormally
> large number of times, but to see that, I need (a) how many times was
> each function called, and (b) across how many characters did Emacs
> move during the time the profile was collected.  This is so I could
> calculate how many times each of the functions was called
> _per_character_move_.

I have simplified the test to enable the problematic settings and move just once across the line of 56 chars.  This time was very slow; it reports:

  Commencing move to end of line with 56 chars [20625]!
  Done in (62.586448000000004 18 0.8153190000000023)!

A total of 3383 characters in simple.el were visible during this movement, and one other frame (monitoring *Messages*) was as well.  I have expanded all of the relevant profile data, so the sample data are larger; see attached.   

The columns are:

   # Samples, Self # Samples, Weight (total time), Self Weight (total time in function body)

This (1ms) sampling profile unfortunately doesn't contain call count.  I could try to take a look, but you probably have better ideas than me about which functions may be called overly often.

[1] https://github.com/jdtsmith/ultra-scroll/issues/32