orderless-try-completion performance
minad opened this issue · comments
Hi Omar,
after #95 it is me again complaining about orderless-try-completion
:-P
Can we optimize this function, such that it does not generate the candidate list all the time? I noticed that orderless-try-completion is expensive in my Corfu UI, with which I fiddled a lot recently.
In Affe I use a throw from the all-completions
predicate to jump out of the loop as soon as enough candidates are found. See https://github.com/minad/affe/blob/0ee5e2374339c1a57d36c06818247afeecadc2c5/affe-backend.el#L180-L183. We could do something similar here - jump out of the loop as soon as we found more than one candidate. This is quite an abuse of the predicate but seems to work.
There is one minor complication though - if I recall correctly the file completion table checks the predicate against the symbol (EDIT: It seems that completion-file-name-table does not ignore the predicate for the try action, only for the all action.)file-exists-p
and ignores it in this case in order to avoid tramp slowdown. One would have to do the same here when overwriting/composing the predicate.
Maybe you have a better idea?
Prototype:
(defun orderless-one-or-many (string table &optional pred)
(catch 'orderless--many
(let ((one nil))
;; Abuse all-completions/orderless-filter as a fast search loop
;; Should be more or less allocation-free since our "predicate" always returns nil
(orderless-filter string table
;; (TODO ignore file-exists-p for tramp! - EDIT: Not needed.)
(if pred
(lambda (str)
(when (funcall pred str)
(when one (throw 'orderless--many 'many))
(setq one str))
nil)
(lambda (str)
(when one (throw 'orderless--many 'many))
(setq one str)
nil)))
one)))
(orderless-one-or-many "x" '("foo" "bar" "boo")) ;; nil
(orderless-one-or-many "a" '("foo" "bar" "boo")) ;; "bar"
(orderless-one-or-many "o" '("foo" "bar" "boo")) ;; many
I never thought of try-completion as frequently executed operation, so optimizing it didn't seem to matter. I think default completion only calls it when you press TAB or from commands like minibuffer-complete-and-exit
. Are you saying that corfu calls it often?
I'm just curious about that. The actual PR looks absolutely fine, and I'm happy to merge it.
Yes, I should explain this better. When the capfs are queried and the capfs are non-exclusive they are checked with try-completion
, see completion--capf-wrapper
in minibuffer.el (note the FIXME there).
In Corfu I use completion-try-completion
for better Orderless support. See the advice https://github.com/minad/corfu/blob/b74243e322bd40781f0bb66ecee2cda770159c4/corfu.el#L1128.
Now if you trigger completion manually in Corfu, orderless-filter
is executed three times! First by the corfu--capf-wrapper-advice
, then by default completion, then by corfu--update
. corfu--update
is only executed when default completion does not finish immediately or cycle. (I could get this down to two calls of course if I don't reuse the default completion and replicate the cycling logic.)
If one uses corfu-auto=t
we get two calls to orderless-filter
. First the corfu--capf-wrapper-advice
then corfu--update
.
So this PR is about reducing the startup cost. The optimized version here makes the corfu--capf-wrapper-advice
try completion check basically free and very fast.
I should add - I also didn't consider this performance critical before. It only becomes critical thanks to the capf wrapper advice/fix of Corfu and the excessive usage of non-exclusive capfs (see also https://github.com/minad/cape). In Corfu I may even add a flag corfu-always-nonexclusive
. Furthermore it makes sense to have a fast check to reduce the cost of corfu-auto=t
.
Got it, thanks.