Subject: bug#64735: 29.0.92; find invocations are ~15x slower because of ignores

From: Ihor Radchenko <[email-masked]>
Date: Fri, 21 Jul 2023 13:17:17 +0000

Eli Zaretskii <[email-masked]> writes:

>> > The question is: what is more costly
>> > (a) matching complex regexp && call function or
>> > (b) call function (lambda (fn) (when (and foo (match-string- ... fn)) ...))
>> 
>> (benchmark-run-compiled 10000000 (string-match-p (caar file-name-handler-alist) "/path/to/very/deep/file"))
>> ;; => (1.495432981 0 0.0)
>> (benchmark-run-compiled 10000000 (funcall (lambda (fn) (and nil (string-match-p (caar file-name-handler-alist) fn))) "/path/to/very/deep/file"))
>> ;; => (0.42053276500000003 0 0.0)
>> 
>> Looks like even funcall overheads are not as bad as invoking regexp search.
>
> But "nil" is not a faithful emulation of the real test which will have
> to be put there, is it?

It is, at least in some cases. In other cases, it is list lookup, which
is also faster:

(benchmark-run-compiled 10000000 (funcall (lambda (fn) (and (get 'foo 'jka-compr) (string-match-p (caar file-name-handler-alist) fn))) "/path/to/very/deep/file"))
;; => (0.5831819149999999 0 0.0)

Let me go through default handlers one by one:

file-name-handler-alist is a variable defined in fileio.c.

Value
(("..." . jka-compr-handler)
 (".." . epa-file-handler)
 ("..." . tramp-archive-file-name-handler)
 ("..." . tramp-completion-file-name-handler)
 ("..." . tramp-file-name-handler)
 ("\\`/:" . file-name-non-special))

---- 1 -----

(defun jka-compr-handler (operation &rest args)
  (save-match-data
    (let ((jka-op (get operation 'jka-compr)))
      (if (and jka-op (not jka-compr-inhibit))
	  (apply jka-op args)
	(jka-compr-run-real-handler operation args)))))

skips when `get' fails, and also puts unnecessary `save-match-data'
call, which would better be inside if.

---- 2 -----

(defun epa-file-handler (operation &rest args)
  (save-match-data
    (let ((op (get operation 'epa-file)))
      (if (and op (not epa-inhibit))
          (apply op args)
  	(epa-file-run-real-handler operation args)))))

again checks `get' and also epa-inhitbit. (and again,
`save-match-data' only needed for (apply op args)).

Side note: These handlers essentially force double handler lookup
without skipping already processed handlers when they decide that they
need to delegate to defaults.

---- 3 -----

    (if (not tramp-archive-enabled)
        ;; Unregister `tramp-archive-file-name-handler'.
        (progn
          (tramp-register-file-name-handlers)
          (tramp-archive-run-real-handler operation args))
          <...>

Note how this tries to remove itself from handler list, by testing a
boolean variable (nil by default!). However, this "self-removal" will
never happen unless we happen to query a file with matching regexp. If
no archive file is accessed during Emacs session (as it is the case for
me), this branch of code will never be executed and I am doomed to have
Emacs checking for regexp in front of this handler forever.

------ 4 ------

(defun tramp-completion-file-name-handler (operation &rest args)
  "Invoke Tramp file name completion handler for OPERATION and ARGS.
Falls back to normal file name handler if no Tramp file name handler exists."
  (if-let
      ((fn (and tramp-mode minibuffer-completing-file-name
		(assoc operation tramp-completion-file-name-handler-alist))))
      (save-match-data (apply (cdr fn) args))
    (tramp-run-real-handler operation args)))

is checking for tramp-mode (t by default) and
minibuffer-completing-file-name (often nil).

-------- 5 --------

(defun tramp-file-name-handler (operation &rest args)
  "Invoke Tramp file name handler for OPERATION and ARGS.
Fall back to normal file name handler if no Tramp file name handler exists."
  (let ((filename (apply #'tramp-file-name-for-operation operation args))
     <...>
    (if (tramp-tramp-file-p filename) ;; <<--- always nil when tramp-mode is nil
    <do staff>
    ;; When `tramp-mode' is not enabled, or the file name is quoted,
      ;; we don't do anything.
      (tramp-run-real-handler operation args))

this one is more complex, but does nothing when tramp-mode is nil.

--------- 6 -------

file-name-non-special is complex.
The only thing I noticed is that it binds tramp-mode as

(let ((tramp-mode (and tramp-mode (eq method 'local-copy))))

So, other handlers checking for tramp-mode variable early would benefit
if they were able to do so.

-- 
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>