Subject: bug#49264: 28.0.50; project.el+tramp performance issue

From: Dmitry Gutov <[email-masked]>
Date: Thu, 19 Aug 2021 04:19:20 +0300

Hi Ergus,

On 17.08.2021 03:45, Ergus wrote:

> I made a manual fast profiling and I see that most of the time in
> project-buffers actually goes to tramp-sh-file-name-handler.
> 
>           207  42%         - project-buffers
>           207  42%          - apply
>           207  42%           - #<compiled -0x1f5d919efefc0a09>
>           200  40%            - file-in-directory-p
>           200  40%             - tramp-file-name-handler
>           197  40%              - apply
>           197  40%               - tramp-sh-file-name-handler
>           197  40%                - tramp-handle-file-in-directory-p
>           183  37%                 - tramp-run-real-handler
>           183  37%                  - file-in-directory-p
>           124  25%                   - file-equal-p
>           124  25%                    - tramp-file-name-handler
>           121  24%                     - apply
>           121  24%                      - tramp-sh-file-name-handler
>           121  24%                       - tramp-handle-file-equal-p
>            85  17%                        - tramp-run-real-handler
>            85  17%                         - file-equal-p
>            52  10%                          - file-truename
>            52  10%                           - tramp-file-name-handler
>            41   8%                            - apply
>            41   8%                             - tramp-sh-file-name-handler
>            41   8%                              - 
> tramp-sh-handle-file-truename
>            28   5%                               + file-remote-p
>            10   2%                               + file-local-name
>             3   0%                               + file-name-as-directory
> 
> It goes specifically to file-in-directory-p as you said. So maybe the
> improvement there may be also desirable if the difference after the
> optimization can reduce the time for file-in-directory-p (or the caller)
> at least to the half.

Thanks for testing.

Try the attached new version please. It should eliminate that particular 
bottleneck.
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index 4620ea8f47..f9b302bb2b 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -51,6 +51,11 @@
 ;; files and its relations to external directories.  `project-files'
 ;; should be consistent with `project-ignores'.
 ;;
+;; `project-buffers' can be overridden if the project has some unusual
+;; shape (e.g. it contains files residing outside of its root, or some
+;; files inside the root must not be considered a part of it). It
+;; should be consistent with `project-files'.
+;;
 ;; This list can change in future versions.
 ;;
 ;; VC project:
@@ -334,6 +339,16 @@ project--remote-file-names
                 (concat remote-id file))
               local-files))))
 
+(cl-defgeneric project-buffers (project)
+  "Return the list of all live buffers that belong to PROJECT."
+  (let ((root (expand-file-name (file-name-as-directory (project-root project))))
+        bufs)
+    (dolist (buf (buffer-list))
+      (when (string-prefix-p root (expand-file-name
+                                   (buffer-local-value 'default-directory buf)))
+        (push buf bufs)))
+    (nreverse bufs)))
+
 (defgroup project-vc nil
   "Project implementation based on the VC package."
   :version "25.1"
@@ -628,6 +643,23 @@ project--value-in-dir
       (hack-dir-local-variables-non-file-buffer))
     (symbol-value var)))
 
+(cl-defmethod project-buffers ((project (head vc)))
+  (let* ((root (expand-file-name (file-name-as-directory (project-root project))))
+         (modules (unless (or (project--vc-merge-submodules-p root)
+                              (project--submodule-p root))
+                    (mapcar
+                     (lambda (m) (format "%s%s/" root m))
+                     (project--git-submodules))))
+         dd
+         bufs)
+    (dolist (buf (buffer-list))
+      (setq dd (expand-file-name (buffer-local-value 'default-directory buf)))
+      (when (and (string-prefix-p root dd)
+                 (not (cl-find-if (lambda (module) (string-prefix-p module dd))
+                                  modules)))
+        (push buf bufs)))
+    (nreverse bufs)))
+
 
 ;;; Project commands
 
@@ -1014,13 +1046,11 @@ project--read-project-buffer
          (current-buffer (current-buffer))
          (other-buffer (other-buffer current-buffer))
          (other-name (buffer-name other-buffer))
+         (buffers (project-buffers pr))
          (predicate
           (lambda (buffer)
             ;; BUFFER is an entry (BUF-NAME . BUF-OBJ) of Vbuffer_alist.
-            (and (cdr buffer)
-                 (equal pr
-                        (with-current-buffer (cdr buffer)
-                          (project-current)))))))
+            (memq (cdr buffer) buffers))))
     (read-buffer
      "Switch to buffer: "
      (when (funcall predicate (cons other-name other-buffer))
@@ -1160,7 +1190,7 @@ project--buffers-to-kill
 What buffers should or should not be killed is described
 in `project-kill-buffer-conditions'."
   (let (bufs)
-    (dolist (buf (project--buffer-list pr))
+    (dolist (buf (project-buffers pr))
       (when (project--kill-buffer-check buf project-kill-buffer-conditions)
         (push buf bufs)))
     bufs))