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

From: Eli Zaretskii <[email-masked]>
Date: Sat, 14 Jun 2025 16:14:20 +0300

> Cc: [email-masked]
> Date: Sat, 14 Jun 2025 09:59:09 +0300
> From: Eli Zaretskii <[email-masked]>
> 
> > It shows that the iterator movement is not in and of itself slow, but that it is called many times per character movement, which was your request before.
> 
> True, but it's hardly new information.  Problems with slow redisplay
> happen not because set_iterator_to_next is slow, but because it is
> called too many times, for whatever reasons.  It is those reasons that
> are interesting, because the way to make redisplay fast enough is to
> eliminate the reasons for those many calls.
> 
> > > set_iterator_to_next is too low-level to explain what's going on.  It
> > > is expected that it will be called many times, but the question is
> > > why?  
> > 
> > I can see that, but on the other hand, it would be strange to expect it to be called 2850x as many times per character movement when a setting is toggled from nil to t.  That seems to me quite excessive.  Your intuitions here may be better.
> 
> Which is why I'm asking why these many calls happen.  What my
> intuition tells was described up-thread, but what we need here is
> facts: why does Emacs in fact try to redraw this window's characters
> so many times?
> 
> > > And the answer to that is at higher levels, at the level of the
> > > functions called by redisplay_window.
> > > IOW, if we call set_iterator_to_next so many times, we either (a)
> > > redraw the entire window many times, or (b) redraw some small subset
> > > of the window's lines even more times.  Which one(s) of these actually
> > > happen and why is the interesting question.
> > 
> > I'm happy to perform call count and duration stats for other functions if you want to suggest some, but it sounds like you may be better positioned to quickly drill down on this.
> 
> The way to answer these questions is to step through the code in
> redisplay_window and see what it does and why in that case.  If
> there's no answer to this question by the time I get enough free time
> to do it myself, I will.

I've now looked at this.  It's like I guessed: this case of having the
cursor in the first visible line that is only partially visible is not
supported, and the code just loops until we forcibly stop that:

      /* If cursor ends up on a partially visible line,
	 treat that as being off the bottom of the screen.  */
      if (! cursor_row_fully_visible_p (w, extra_scroll_margin_lines <= 1,
					false, false)
	  /* It's possible that the cursor is on the first line of the
	     buffer, which is partially obscured due to a vscroll
	     (Bug#7537).  In that case, avoid looping forever. */
	  && extra_scroll_margin_lines < w->desired_matrix->nrows - 1)
	{
	  clear_glyph_matrix (w->desired_matrix);
	  ++extra_scroll_margin_lines;
	  goto too_near_end;
	}

So what happens here is that code loops as many times as there are
screen lines in the window, and then bails out.

A simple solution I can offer is in the patch below.  Please give it
enough testing to see that it doesn't cause regressions elsewhere in
redisplay.  If it doesn't, I will install it.

diff --git a/src/xdisp.c b/src/xdisp.c
index 27094f8..c3c4315 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -19447,15 +19447,19 @@ try_scrolling (Lisp_Object window, bool just_this_one_p,
       /* If cursor ends up on a partially visible line,
 	 treat that as being off the bottom of the screen.  */
       if (! cursor_row_fully_visible_p (w, extra_scroll_margin_lines <= 1,
-					false, false)
-	  /* It's possible that the cursor is on the first line of the
-	     buffer, which is partially obscured due to a vscroll
-	     (Bug#7537).  In that case, avoid looping forever. */
-	  && extra_scroll_margin_lines < w->desired_matrix->nrows - 1)
+					false, false))
 	{
 	  clear_glyph_matrix (w->desired_matrix);
 	  ++extra_scroll_margin_lines;
-	  goto too_near_end;
+	  /* It's possible that the cursor is on the first line of the
+	     buffer, which is partially obscured due to a vscroll
+	     (Bug#7537).  In that case, just fail, since the code above
+	     is not prepared to deal with that case.  */
+	  if (MATRIX_ROW_PARTIALLY_VISIBLE_AT_TOP_P
+		(w, MATRIX_ROW (w->desired_matrix, w->cursor.vpos)))
+	    rc = SCROLLING_FAILED;
+	  else
+	    goto too_near_end;
 	}
       rc = SCROLLING_SUCCESS;
     }