Subject: bug#41029: closed (Re: bug#41029: Improve ido-switch-buffer performance when many buffers are open)

From: [email-masked] (GNU bug Tracking System)
Date: Thu, 28 May 2020 23:37:02 +0000

Your bug report

#41029: Improve ido-switch-buffer performance when many buffers are open

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
41029: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=41029
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#41029: Improve ido-switch-buffer performance when many buffers are open
From: Dmitry Gutov <[email-masked]>
To: Arnold Noronha <[email-masked]>, [email-masked]
Date: Fri, 29 May 2020 02:36:31 +0300
List-Id: <debbugs-submit.debbugs.gnu.org>

Version: 28.1

On 03.05.2020 05:03, Dmitry Gutov wrote:
> On 02.05.2020 21:26, Arnold Noronha wrote:
>> Actually, I realized this could be cleaner. Nicer patch attached.
> 
> Looks fine to me, thanks. Unless someone expresses any objections in a 
> week or so, we should install it.

Now pushed to master. Thank you!

Subject: Improve ido-switch-buffer performance when many buffers are open
From: Arnold Noronha <[email-masked]>
To: [email-masked]
Date: Sat, 2 May 2020 08:40:55 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Many of the buffer locals (in particular I think it's the
SYMBOL_FORWARDED types --- my terminoly may be off), appear to be O(n)
to bind, where n is the number of live buffers.

I tried to see if I can optimize that binding process directly, but
couldn't figure out a way to do it without changing current behavior,
so I opted to just optimize ido-switch-buffer directly.

After this patch, the performance is quite bearable at ~6000 buffers,
but still not ideal.
>From 3bc4eb78c365f4b4a387451f0b55264076a97c4b Mon Sep 17 00:00:00 2001
From: Arnold Noronha <[email-masked]>
Date: Sat, 2 May 2020 08:30:47 -0700
Subject: [PATCH] Avoid binding CASE-FOLD-SEARCH in tight loop

IDO-IGNORE-ITEM-P is called for every buffer when switching
buffers. However, that function binds CASE-FOLD-SEARCH, which
appears to be an O(n) operation where n is (length (buffer-list)).
So we avoid binding it if we know we don't need to as a workaround.

To test this, this this:

(loop for i from 0 to 6000
      do
      (find-file (format "/tmp/xy%d.txt" i)))

Then try switching buffers with and without this patch.
---
 lisp/ido.el | 67 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 36 insertions(+), 31 deletions(-)

diff --git a/lisp/ido.el b/lisp/ido.el
index 81883402ad..d7a24ae1d7 100644
--- a/lisp/ido.el
+++ b/lisp/ido.el
@@ -3408,7 +3408,7 @@ ido-make-merged-file-list
 (defun ido-make-buffer-list-1 (&optional frame visible)
   "Return list of non-ignored buffer names."
   (delq nil
-	(mapcar
+       (mapcar
 	 (lambda (x)
 	   (let ((name (buffer-name x)))
 	     (if (not (or (ido-ignore-item-p name ido-ignore-buffers) (member name visible)))
@@ -3823,36 +3823,41 @@ ido-ignore-item-p
       (and
        ido-process-ignore-lists re-list
        (save-match-data
-	 (let ((ext-list (and ignore-ext ido-ignore-extensions
-			      completion-ignored-extensions))
-	       (case-fold-search ido-case-fold)
-	       ignorep nextstr
-	       (flen (length name)) slen)
-	   (while ext-list
-	     (setq nextstr (car ext-list))
-	     (if (cond
-		  ((stringp nextstr)
-		   (and (>= flen (setq slen (length nextstr)))
-			(string-equal (substring name (- flen slen)) nextstr)))
-		  ((functionp nextstr) (funcall nextstr name))
-		  (t nil))
-		 (setq ignorep t
-		       ext-list nil
-		       re-list nil)
-	       (setq ext-list (cdr ext-list))))
-	   (while re-list
-	     (setq nextstr (car re-list))
-	     (if (cond
-		  ((stringp nextstr) (string-match nextstr name))
-		  ((functionp nextstr) (funcall nextstr name))
-		  (t nil))
-		 (setq ignorep t
-		       re-list nil)
-	       (setq re-list (cdr re-list))))
-	   ;; return the result
-	   (if ignorep
-	       (setq ido-ignored-list (cons name ido-ignored-list)))
-	   ignorep)))))
+         (flet ((inner ()
+                       (let ((ext-list (and ignore-ext ido-ignore-extensions
+			                    completion-ignored-extensions))
+	                     ignorep nextstr
+	                     (flen (length name)) slen)
+	                 (while ext-list
+	                   (setq nextstr (car ext-list))
+	                   (if (cond
+		                ((stringp nextstr)
+		                 (and (>= flen (setq slen (length nextstr)))
+			              (string-equal (substring name (- flen slen)) nextstr)))
+		                ((functionp nextstr) (funcall nextstr name))
+		                (t nil))
+		               (setq ignorep t
+		                     ext-list nil
+		                     re-list nil)
+	                     (setq ext-list (cdr ext-list))))
+	                 (while re-list
+	                   (setq nextstr (car re-list))
+	                   (if (cond
+		                ((stringp nextstr) (string-match nextstr name))
+		                ((functionp nextstr) (funcall nextstr name))
+		                (t nil))
+		               (setq ignorep t
+		                     re-list nil)
+	                     (setq re-list (cdr re-list))))
+	                 ;; return the result
+	                 (if ignorep
+	                     (setq ido-ignored-list (cons name ido-ignored-list)))
+	                 ignorep)))
+
+           (if (eq case-fold-search ido-case-fold)
+               (inner)
+             (let ((case-fold-search ido-case-fold))
+               (inner))))))))
 
 ;; Private variable used by `ido-word-matching-substring'.
 (defvar ido-change-word-sub)
-- 
2.20.1