-
-
Notifications
You must be signed in to change notification settings - Fork 340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Default fuzzy searching speed #848
Comments
Can you provide an example collection that you're using ivy's completion on. Example: a big file and a list of terms to search for. I can't really reproduce your Projectile project. |
Will do, as time allows. |
This will work. Searching for 'adengine' will reproduce the behavior. https://github.com/jojojames/ivy-fuzzy-sample/tree/master/MyProfile |
It seems that your project is stressing the limits of the regex engine. Entering
Running the function results in the following time -
So all we do here is test the above regex on 8519 strings and it takes that much time. Even simplifying the regex to The only thing that I can think of that can improve the computation time is performing the regex matching incrementally, i.e. assume all matches for @PythonNut, thoughts on this? |
@abo-abo I'm not sure yet. I have several ideas, but I might not have time to check them for a while.
Also, @jojojames the code in the advice I wrote is essentially the same as the code that's currently deployed by |
@PythonNut The results are different (at least the first few) for keyword 'adengine' |
@jojojames that seems to imply to me that the fuzzy sorting is being bypassed for some reason, which is why it's so fast. |
That's strange. I wonder what kind of matching I'm getting then. In another project, Typing 'abc', I'll get results like this, src/Something/[A]pp.ZZZ/[B]aseActivity.[c]s and so on. |
@jojojames I can't really tell, but that doesn't look right. |
@PythonNut I can try and provide more concrete results given a specific search to narrow down the problem. As an aside, it'd be great if we could somehow integrate (either through Emacs25 modules or through a background process) something like this junegunn/fzf#764. Vim has always had really fast fuzzy finders. |
I was running into this too, and the idea that popped into my head was (for files anyway): apply the flx regex to each path portion, instead of across the whole path. So, break Maybe someone could help me prototype this? With https://github.com/Microsoft/openenclave,
(Note that I figure 99% of what someone wants when using flx on a file path is to match against the file and directory name components. Even better would be if |
In my profiling, most of the time from jojojames's example is spent in (defun my:ivy--regex-fuzzy (str)
"Build a regex sequence from STR.
Insert the moral equivalent of .* between each char."
(if (string-match "\\`\\(\\^?\\)\\(.*?\\)\\(\\$?\\)\\'" str)
(prog1
(concat (match-string 1 str)
(cl-loop
for ch across (match-string 2 str)
for sep = "" then (format "[^%c]*" ch)
concat sep
concat (format "\\(%s\\)"
(regexp-quote (char-to-string ch))))
(match-string 3 str))
(setq ivy--subexps (length (match-string 2 str))))
str))
I ran this several times and those times are fairly stable. (Next paragraph edited from original post.) One thing I have noticed is that I think the matches I get are sorted differently, and I don't know why. The set of candidates ends up being the same in this one case, but I am concerned that the using The only other idea I had for speeding up the existing fuzzy matching is to notice when I type faster than the candidates can be filtered: Right now I can type "adengine" and Ivy will search for "a" then "ad" then "ade", and so on. However, by the time it's searching "aden", for example, I believe I have already completed typing my whole search string, "adengine". Could Ivy try to consume more than just the single next character of input when updating the candidates? In other words, if filtering on "aden" takes 400 ms, and by the time that search is complete I've added the remaining "gine" (of "adengine") to the input buffer, could Ivy skip searching "adeng", "adengi", "adengin" and just skip straight to searching "adengine"? Still seems unfortunate that there may be that long pause between "aden" and "adengine" but it's better than multiple long pauses, I think. |
There is some relation with #1812, where |
Answering my own question:
Mystery solved, I think: The advice: ;; Must get `my:ivy--regex-fuzzy' to sort as with `ivy--regex-fuzzy'.
(define-advice ivy--sort
(:around (orig-fun &rest args) my:sort-my-fuzzy-with-flx)
(let ((ivy--regex-function (if (eq ivy--regex-function
'my:ivy--regex-fuzzy)
'ivy--regex-fuzzy
ivy--regex-function)))
(apply orig-fun args))) |
@dsedivec Thanks for the detailed benchmark. I like your idea of switching from a non-greedy regex to a greedy one. That should speed up things. |
* ivy-test.el (ivy--regex-fuzzy): Update tests. Thanks to @PythonNut and @dsedivec for the idea. Re #848
I've prototyped a version of (defun counsel-describe-function-fzf-function (str)
(setq ivy--old-re (ivy--regex-fuzzy str))
(or (ivy-more-chars)
(progn
(counsel--async-command
(format "cat /tmp/counsel-describe-function-fzf | fzf -f '%s'" str))
nil)))
(defun counsel-describe-function-fzf ()
"Forward to `describe-function'.
Interactive functions (i.e., commands) are highlighted according
to `ivy-highlight-face'."
(interactive)
(with-temp-buffer
(insert (mapconcat #'identity
(all-completions "" obarray
(lambda (sym)
(or (fboundp sym)
(get sym 'function-documentation))))
"\n"))
(write-file "/tmp/counsel-describe-function-fzf"))
(let ((enable-recursive-minibuffers t))
(ivy-read "Describe function: " #'counsel-describe-function-fzf-function
:require-match t
:history 'counsel-describe-symbol-history
:keymap counsel-describe-map
:preselect (funcall counsel-describe-function-preselect)
:action (lambda (x)
(funcall counsel-describe-function-function (intern x)))
:dynamic-collection t
:caller 'counsel-describe-function-fzf))) A second improvement would be to use |
Here's an alternative way of generating the temporary file which should also enable recursive completion sessions: diff --git a/counsel.el b/counsel.el
index f6ff507..2b87e31 100644
--- a/counsel.el
+++ b/counsel.el
@@ -1,10 +1,12 @@
+(defvar counsel--describe-function-fzf-file null-device
+ "Input file for `counsel-describe-function-fzf-function'.")
+
(defun counsel-describe-function-fzf-function (str)
(setq ivy--old-re (ivy--regex-fuzzy str))
(or (ivy-more-chars)
- (progn
- (counsel--async-command
- (format "cat /tmp/counsel-describe-function-fzf | fzf -f '%s'" str))
- nil)))
+ (ignore (counsel--async-command
+ (format "fzf -f '%s' < '%s'"
+ str counsel--describe-function-fzf-file)))))
(defun counsel-describe-function-fzf ()
"Forward to `describe-function'.
@@ -12,15 +14,13 @@ counsel-describe-function-fzf
Interactive functions (i.e., commands) are highlighted according
to `ivy-highlight-face'."
(interactive)
- (with-temp-buffer
- (insert (mapconcat #'identity
- (all-completions "" obarray
- (lambda (sym)
- (or (fboundp sym)
- (get sym 'function-documentation))))
- "\n"))
- (write-file "/tmp/counsel-describe-function-fzf"))
- (let ((enable-recursive-minibuffers t))
+ (let* ((enable-recursive-minibuffers t)
+ (fns (all-completions "" obarray
+ (lambda (sym)
+ (or (fboundp sym)
+ (get sym 'function-documentation)))))
+ (counsel--describe-function-fzf-file
+ (make-temp-file "counsel-" nil nil (mapconcat #'identity fns "\n"))))
(ivy-read "Describe function: " #'counsel-describe-function-fzf-function
:require-match t
:history 'counsel-describe-symbol-history Only problem is, the temporary file should be deleted when it's no longer needed. (Note that |
I implemented a completion-style supporting various fuzzy matching backends. It includes support for the rust based/native module/c backends that (from quick benchmarking) are 10x faster than the elisp flx option. https://github.com/jojojames/fussy For ivy to be able to leverage this though, looks like we need to implement completion-styles support (#2321 here?). |
Took a quick look and found I can just stub in the flx-score call here with fussy's call.
The wins don't seem that great in terms of performance (slight win?) but at least you'd be able to select the scoring backend to use instead of just flx. Not sure, I didn't benchmark perfectly. I think this area could use some improvement as it seems quite slow compared to the selectrum/vertico/icomplete etc. On the other hand, the equivalent call (M-x describe-symbol) with 30,000 candidates can be filtered in about 30-40 ms with vertico+fussy. (See https://github.com/jojojames/fussy#fast for a handwavy benchmark) whereas the calls with flx+ido using this ivy-sort here goes past 100ms. (Not sure if if it was much more than that but definitely slower than vertico+fussy) Think it may be because we're doing 2-3 phases of filtering (very slow) before we finally get to scoring. e.g. I'm guessing we filter initially for the collection, then here, we're filtering again! using the fuzzy regex and then finally we're scoring. You can see in the implementation here (for completion-styles support), we just filter once (through the quick C all-completions passing in the flex regex to it, and then scoring over the filtered candidates. |
The default fuzzy speed seems to be a little slow when I compare it with evaling this snippet from
#207
I included a video here.
https://dl.dropboxusercontent.com/u/11400324/Untitled.m4v
When I type 'adengine' with the default ivy fuzzy, it has a noticeable 1-2 second pause before going through with the search. With the eval'd code, I can get through the entire adengine text with only minor pauses.
Using ivy-20170105.711
My own ivy config
The text was updated successfully, but these errors were encountered: