Subject: Re: bug#66117: 30.0.50; `find-buffer-visiting' is slow when opening large number of buffers

From: Ihor Radchenko <[email-masked]>
Date: Fri, 15 Dec 2023 11:01:53 +0000

Stefan Monnier <[email-masked]> writes:

>> Do I understand correctly that what is needed to implement your
>> suggestion is
>>
>> 1. Remove case_fold_search_ slot from buffer objects
>> 2. Use DEFVAR_LISP for case-fold-search
>> 3. Declare case-fold-search buffer-local
>> 4. Replace BVAR(...) references in C with Vcase_fold_search
>
> Yes (assuming that 3 refers to the use of `make-variable-buffer-local`).

See the attached patch.

I used my `find-buffer-visiting' benchmark

(dolist (file (directory-files "/tmp/test/" t "txt"))
			   (find-file-noselect file))

Without the patch: 10.3 sec
With the patch:     7.0 sec

Without the patch, the reverse profiler tree is
        2728  26%   Automatic GC
        1187  11% + abbreviate-file-name                    ;; <---
        1080  10% + inhibit-local-variables-p               ;; <---
         913   8% + uniquify-rationalize-file-buffer-names
         816   7% + find-buffer-visiting
         547   5% + locate-dominating-file

With the patch, the `abbreviate-file-name' and
`inhibit-local-variables-p' no longer appear on top

        2559  35%   Automatic GC
         892  12% + uniquify-rationalize-file-buffer-names
         704   9% + find-buffer-visiting
         473   6% + locate-dominating-file
         295   4% + dir-locals--all-files
         241   3% + generate-new-buffer
         219   3% + file-truename
         169   2% + inhibit-local-variables-p
         128   1% + abbreviate-file-name

>From fb7677be03e8c49d6a549f0d993b4df6f0ea404d Mon Sep 17 00:00:00 2001
Message-ID: <[email-masked]>
From: Ihor Radchenko <[email-masked]>
Date: Fri, 15 Dec 2023 11:47:45 +0100
Subject: [PATCH] Improve performance let-binding `case-fold-search'
 (bug#66117)

* src/buffer.h: Remove case_fold_search_ buffer object slot.
* src/buffer.c (bset_case_fold_search): Remove - no longer needed.
(init_buffer_once): Remove removed buffer slot init.
(syms_of_buffer): Use DEFVAR_LISP to define `case-fold-search' and
declare it buffer-local.
* src/minibuf.c (syms_of_minibuf): Remove DEFSYM call for
`case-fold-search' symbol.  It now lives in `syms_of_buffer'.
* src/editfns.c (Fcompare_buffer_substrings):
(Fchar_equal):
* src/search.c (looking_at_1):
(string_match_1):
(search_command):
(Fre__describe_compiled): Adjust C queries to `case-fold-search' value
to use C globals instead of BVAR macro.
* doc/lispref/internals.texi (Buffer Internals): Do not list
`case_fold_search' slot.

See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=66117#259

When used as buffer slot, let-binding `case-fold-search' would scale
with the number of live buffers and can be slow.  This change makes
let-binding much faster at the cost of slightly slower `set-buffer'.
---
 doc/lispref/internals.texi |  1 -
 src/buffer.c               | 17 ++++++-----------
 src/buffer.h               |  1 -
 src/editfns.c              |  4 ++--
 src/minibuf.c              |  1 -
 src/search.c               | 10 +++++-----
 6 files changed, 13 insertions(+), 21 deletions(-)

diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi
index 1fba683223e..4b4edda6d46 100644
--- a/doc/lispref/internals.texi
+++ b/doc/lispref/internals.texi
@@ -2519,7 +2519,6 @@ Buffer Internals
 
 @item mode_line_format
 @itemx header_line_format
-@itemx case_fold_search
 @itemx tab_width
 @itemx fill_column
 @itemx left_margin
diff --git a/src/buffer.c b/src/buffer.c
index 12f226d8249..919c470d9e5 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -210,11 +210,6 @@ bset_buffer_file_coding_system (struct buffer *b, Lisp_Object val)
   b->buffer_file_coding_system_ = val;
 }
 static void
-bset_case_fold_search (struct buffer *b, Lisp_Object val)
-{
-  b->case_fold_search_ = val;
-}
-static void
 bset_ctl_arrow (struct buffer *b, Lisp_Object val)
 {
   b->ctl_arrow_ = val;
@@ -4692,7 +4687,6 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, mode_line_format), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, abbrev_mode), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, overwrite_mode), idx); ++idx;
-  XSETFASTINT (BVAR (&buffer_local_flags, case_fold_search), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, auto_fill_function), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, selective_display), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, selective_display_ellipses), idx); ++idx;
@@ -4785,7 +4779,6 @@ init_buffer_once (void)
   bset_tab_line_format (&buffer_defaults, Qnil);
   bset_abbrev_mode (&buffer_defaults, Qnil);
   bset_overwrite_mode (&buffer_defaults, Qnil);
-  bset_case_fold_search (&buffer_defaults, Qt);
   bset_auto_fill_function (&buffer_defaults, Qnil);
   bset_selective_display (&buffer_defaults, Qnil);
   bset_selective_display_ellipses (&buffer_defaults, Qt);
@@ -5215,10 +5208,6 @@ syms_of_buffer (void)
 		     doc: /*  Non-nil if Abbrev mode is enabled.
 Use the command `abbrev-mode' to change this variable.  */);
 
-  DEFVAR_PER_BUFFER ("case-fold-search", &BVAR (current_buffer, case_fold_search),
-		     Qnil,
-		     doc: /* Non-nil if searches and matches should ignore case.  */);
-
   DEFVAR_PER_BUFFER ("fill-column", &BVAR (current_buffer, fill_column),
 		     Qintegerp,
 		     doc: /* Column beyond which automatic line-wrapping should happen.
@@ -5951,6 +5940,12 @@ Functions (implicitly) running this hook are `get-buffer-create',
 This is the default.  If nil, auto-save file deletion is inhibited.  */);
   delete_auto_save_files = 1;
 
+  DEFVAR_LISP ("case-fold-search", Vcase_fold_search,
+	       doc: /* Non-nil if searches and matches should ignore case.  */);
+  Vcase_fold_search = Qt;
+  DEFSYM (Qcase_fold_search, "case-fold-search");
+  Fmake_variable_buffer_local (Qcase_fold_search);
+
   DEFVAR_LISP ("clone-indirect-buffer-hook", Vclone_indirect_buffer_hook,
 	       doc: /* Normal hook to run in the new buffer at the end of `make-indirect-buffer'.
 
diff --git a/src/buffer.h b/src/buffer.h
index b2bd15657dc..399c6585158 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -379,7 +379,6 @@ #define BVAR(buf, field) ((buf)->field ## _)
   /* Values of several buffer-local variables.  */
   /* tab-width is buffer-local so that redisplay can find it
      in buffers that are not current.  */
-  Lisp_Object case_fold_search_;
   Lisp_Object tab_width_;
   Lisp_Object fill_column_;
   Lisp_Object left_margin_;
diff --git a/src/editfns.c b/src/editfns.c
index 49d552c4a75..aa410ea7091 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -1785,7 +1785,7 @@ DEFUN ("compare-buffer-substrings", Fcompare_buffer_substrings, Scompare_buffer_
   register EMACS_INT begp1, endp1, begp2, endp2, temp;
   register struct buffer *bp1, *bp2;
   register Lisp_Object trt
-    = (!NILP (BVAR (current_buffer, case_fold_search))
+    = (!NILP (Vcase_fold_search)
        ? BVAR (current_buffer, case_canon_table) : Qnil);
   ptrdiff_t chars = 0;
   ptrdiff_t i1, i2, i1_byte, i2_byte;
@@ -4367,7 +4367,7 @@ DEFUN ("char-equal", Fchar_equal, Schar_equal, 2, 2, 0,
 
   if (XFIXNUM (c1) == XFIXNUM (c2))
     return Qt;
-  if (NILP (BVAR (current_buffer, case_fold_search)))
+  if (NILP (Vcase_fold_search))
     return Qnil;
 
   i1 = XFIXNAT (c1);
diff --git a/src/minibuf.c b/src/minibuf.c
index 58adde1bf66..0d5b375e450 100644
--- a/src/minibuf.c
+++ b/src/minibuf.c
@@ -2320,7 +2320,6 @@ syms_of_minibuf (void)
 
   DEFSYM (Qcurrent_input_method, "current-input-method");
   DEFSYM (Qactivate_input_method, "activate-input-method");
-  DEFSYM (Qcase_fold_search, "case-fold-search");
   DEFSYM (Qmetadata, "metadata");
   DEFSYM (Qcycle_sort_function, "cycle-sort-function");
 
diff --git a/src/search.c b/src/search.c
index 2996d32fca1..75a67ff3223 100644
--- a/src/search.c
+++ b/src/search.c
@@ -281,7 +281,7 @@ looking_at_1 (Lisp_Object string, bool posix, bool modify_data)
   struct regexp_cache *cache_entry = compile_pattern (
     string,
     modify_match_data ? &search_regs : NULL,
-    (!NILP (BVAR (current_buffer, case_fold_search))
+    (!NILP (Vcase_fold_search)
      ? BVAR (current_buffer, case_canon_table) : Qnil),
     posix,
     !NILP (BVAR (current_buffer, enable_multibyte_characters)));
@@ -402,7 +402,7 @@ string_match_1 (Lisp_Object regexp, Lisp_Object string, Lisp_Object start,
   struct regexp_cache *cache_entry
     = compile_pattern (regexp,
 		       modify_match_data ? &search_regs : NULL,
-		       (!NILP (BVAR (current_buffer, case_fold_search))
+		       (!NILP (Vcase_fold_search)
 			? BVAR (current_buffer, case_canon_table)
 			: Qnil),
 		       posix,
@@ -1068,10 +1068,10 @@ search_command (Lisp_Object string, Lisp_Object bound, Lisp_Object noerror,
 			 BVAR (current_buffer, case_eqv_table));
 
   np = search_buffer (string, PT, PT_BYTE, lim, lim_byte, n, RE,
-		      (!NILP (BVAR (current_buffer, case_fold_search))
+		      (!NILP (Vcase_fold_search)
 		       ? BVAR (current_buffer, case_canon_table)
 		       : Qnil),
-		      (!NILP (BVAR (current_buffer, case_fold_search))
+		      (!NILP (Vcase_fold_search)
 		       ? BVAR (current_buffer, case_eqv_table)
 		       : Qnil),
 		      posix);
@@ -3402,7 +3402,7 @@ DEFUN ("re--describe-compiled", Fre__describe_compiled, Sre__describe_compiled,
 {
   struct regexp_cache *cache_entry
     = compile_pattern (regexp, NULL,
-                       (!NILP (BVAR (current_buffer, case_fold_search))
+                       (!NILP (Vcase_fold_search)
                         ? BVAR (current_buffer, case_canon_table) : Qnil),
                        false,
                        !NILP (BVAR (current_buffer,
-- 
2.42.0

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>