Subject: bug#56682: Fix the long lines font locking related slowdowns

From: Dmitry Gutov <[email-masked]>
Date: Fri, 5 Aug 2022 04:39:46 +0300

On 04.08.2022 16:09, Eli Zaretskii wrote:

>> That's the scenario I described, and that's my point: this file's
>> display is sluggish. Even though font-lock has already finished its
>> work. And it didn't have to spend any significant time in syntax-ppss.
>>
>> So there is a particular performance problem with the display of
>> fontified buffers which I'd really like your help in fixing.
> 
> Maybe it is in display, and maybe it isn't.  Do you have any evidence
> that the sluggish response is due to redisplay?  C-n, for example, is
> mostly not redisplay, but Lisp code in simple.el and occasional calls
> to vertical-motion.

I come to that conclusion by observing said sluggish movement in a 
buffer that is fully fontified. And yet the delays after pressing 'C-n' 
or 'C-p' or 'C-n' seemed noticeable and very similar to delays on 
PgUp/PgDown.

Are these delays in fontification-functions? That seems unlikely given 
the buffer is fontified already in full, and none of the commands are 
causing modifications (which would force syntax-ppss cache and 
fontifications to be recalculated).

If the delays are in font-lock anyway, that might be in the code which 
checks that the area between window-start and window-end is fontified. I 
don't see why that code has to be slow (long lines or not), so it should 
be fixable easily enough. Unless 'get-text-property' or 
'next-single-property-change' can exhibit pathologic performance in the 
presence of long lines, of course. But that doesn't show up in my testing.

> But even if the slow response is due to redisplay, we just have
> another cause that we need to investigate and try fixing.

It seems to me that that cause actually has larger impact than 
font-lock, because it does show itself in a moderately-sized (88K) 
buffer, where font-lock doesn't feel like a problem. It stands to reason 
that the same "cause" might have a proportionally bigger impact in large 
buffers as well, and only after we remove it (alone), then we can 
evaluate how font-lock itself affects user experience, and how much of 
its correctness (and for buffers of which size) we want to sacrifice.

> It says
> nothing about the measures we've already taken on master.  They
> definitely make even this case faster, and with an unoptimized build I
> can now reasonably edit this file, something I couldn't do before.

If my guess is right, the fix on master whammied all over the redisplay 
with narrowing, both fixing the "cause" and restricting font-lock to the 
same narrowed region. The latter part might be unnecessary in the usual 
case (we might still decide to do that later for much larger buffers, 
but that should be decided by a separate threshold variable).

>> Fixing in a way that doesn't add narrowing around
>> fontification-functions, because as we can see it's not necessary in
>> examples like this.
> 
> If that is possible, sure.  No one said that from now on every problem
> in Emacs that causes slow responses will be handled by narrowing.  But
> if, for example, it turns out that the slow responses is due to time
> it takes some code to traverse a long stretch of fontified buffer,
> what other solution would you suggest except making the portion to be
> traversed shorter?

For all I know, the most optimal fix might still be implemented through 
narrowing, but it would be temporarily widened while 
fontificiation-functions are run.

>>> If you dislike mis-fontification, turn font-lock mode off.  It's as easy
>>> as that.  Mis-fontification is expected in such cases.  The docstring of
>>> syntax-wholeline-max also mentions that "misfontification may then
>>> occur". Why did you not protest at that time?
>>
>> I think we could have both speed and correctness, at least for files of
>> this size.
> 
> That is not a given, and the experience till now suggests otherwise.

I have commented out the code which applies the narrowing in 
'handle_fontified_prop' and recompiled.

The result:

- My 88K file is fontified correctly now. The redisplay and scrolling 
performance seem unaffected (meaning still fast).
- dictionary.json (18M) seems to be fontified correctly as well now 
(it's a mess by default on master), its scrolling performance is 
unaffected too. The difference: I have to wait ~2 seconds the first time 
I press 'M->'.

BTW, 'M-> M-<' triggers some puzzling long wait (~3 seconds) both on 
master and with my change, every time I issue this sequence of commands.

diff --git a/src/xdisp.c b/src/xdisp.c
index 099efed2db..02d7f6c562 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -4391,19 +4391,19 @@ handle_fontified_prop (struct it *it)

        eassert (it->end_charpos == ZV);

-      if (current_buffer->long_line_optimizations_p)
-	{
-	  ptrdiff_t begv = it->narrowed_begv;
-	  ptrdiff_t zv = it->narrowed_zv;
-	  ptrdiff_t charpos = IT_CHARPOS (*it);
-	  if (charpos < begv || charpos > zv)
-	    {
-	      begv = get_narrowed_begv (it->w, charpos);
-	      zv = get_narrowed_zv (it->w, charpos);
-	    }
-	  narrow_to_region_internal (make_fixnum (begv), make_fixnum (zv), true);
-	  specbind (Qrestrictions_locked, Qt);
-	}
+      /* if (current_buffer->long_line_optimizations_p) */
+      /* 	{ */
+      /* 	  ptrdiff_t begv = it->narrowed_begv; */
+      /* 	  ptrdiff_t zv = it->narrowed_zv; */
+      /* 	  ptrdiff_t charpos = IT_CHARPOS (*it); */
+      /* 	  if (charpos < begv || charpos > zv) */
+      /* 	    { */
+      /* 	      begv = get_narrowed_begv (it->w, charpos); */
+      /* 	      zv = get_narrowed_zv (it->w, charpos); */
+      /* 	    } */
+      /* 	  narrow_to_region_internal (make_fixnum (begv), make_fixnum 
(zv), true); */
+      /* 	  specbind (Qrestrictions_locked, Qt); */
+      /* 	} */

        /* Don't allow Lisp that runs from 'fontification-functions'
  	 clear our face and image caches behind our back.  */