Skip to content
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

complete: simple performance improvements (see #64) #65

Merged
merged 2 commits into from Jan 25, 2021

Conversation

timjrd
Copy link
Contributor

@timjrd timjrd commented Nov 1, 2020

This implements what was discussed in #64. I didn't implement a dedicated substr pattern for filenames because it would have required to significantly alter the substr filter and I wanted to keep this patch small. complete_limit is checked only in some cases, namely those where I was facing prohibitive processing time: I didn't want to add checks everywhere without testing them.

My Bash skills are rather limited and this project is fairly complex so please don't hesitate to ask me to fix this PR if necessary. Also, maybe this needs more testing: I'm switching my ble.sh installation to this branch.

Edit: Resolve #64

Copy link
Owner

@akinomyoga akinomyoga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Nice! I have a few discussions.

complete_limit is checked only in some cases, namely those where I was facing prohibitive processing time: I didn't want to add checks everywhere without testing them.

OK. Actually, that was what I intended: we can add checks anytime later when we noticed some performance issues.

My Bash skills are rather limited and this project is fairly complex so please don't hesitate to ask me to fix this PR if necessary.

Your code looks fine! It is not a matter of skill, but there are certain coding styles of ble.sh and also some bugs of old Bash. I will check for such trivial fixes after we settled the design for this new feature. For now, let us discuss the detailed behavior of the new option. I have several comments as will be written in the subsequent review comments.

blerc

I would appreciate it if you could add a description of the new option in blerc template.

substr pattern

Outdated. Please forget about this discussion.

I didn't implement a dedicated substr pattern for filenames because it would have required to significantly alter the substr filter and I wanted to keep this patch small.

I looked into the code, and now I see your point. The current substr filter is so general that it doesn't treat the path separator / in a special way. We might want some pattern like *dir1*/*dir2*/*file* for the path dir1/dir2/file , but the filter only accepts the pattern like *dir1/dir2/file*.

I thought of allowing the skip of filtering when the source itself does the filtering, but the current implementation in ble.sh is not so clean that it is possible. I think I later change to perform the filtering in ble/complete/cand/yield instead of after the call of sources, and then add the way to skip filtering.

For now, to match with the current behavior of substr filter, could you please just generate a pattern as ble/string#quote-word "$path"; local pattern=*$ret* for *:m:*? Edit: I have applied such a change in 7c6b67b along with other cleanups of filtering codes.

See updates in #65 (comment) .


Thank you again for your PR!

lib/core-complete.sh Show resolved Hide resolved
lib/core-complete-def.sh Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
@akinomyoga
Copy link
Owner

akinomyoga commented Nov 2, 2020

I've tried. I'm thinking that we can change the limit for tab-completion and auto-completion, i.e., the latter is smaller than the former. What do you think?

For example, add bleopt complete_limit_auto for the latter. When bleopt complete_limit_auto is empty, the common setting complete_limit is used for auto-completions. It is cumbersome to write such a logic every time, so we can add the following utility and replace [[ XXX -gt $bleopt_complete_limit ]] by ! ble/complete/source/test-limit XXX.

diff --git a/lib/core-complete.sh b/lib/core-complete.sh
index ee90fde..ed10296 100644
--- a/lib/core-complete.sh
+++ b/lib/core-complete.sh
@@ -1312,6 +1312,14 @@ function ble/complete/action:variable/init-menu-item {
 #------------------------------------------------------------------------------
 # source

+function ble/complete/source/test-limit {
+  local value=$1 limit=$bleopt_complete_limit
+  [[ $bleopt_complete_limit_auto && :$comp_type: == *:auto:* ]] &&
+    limit=$bleopt_complete_limit_auto
+  ((limit=limit)) # evaluate arithmetic expression
+  ((limit<=0||value<=limit))
+}
+
 ## 関数 ble/complete/source/reduce-compv-for-ambiguous-match
 ##   曖昧補完の為に擬似的な COMPV と COMPS を生成・設定します。
 ##   @var[in,out] COMPS COMPV

@akinomyoga akinomyoga added NYI/NewFeat Not yet implemented or New Feature performance labels Nov 2, 2020
@timjrd
Copy link
Contributor Author

timjrd commented Nov 5, 2020

Hi! Sorry, I'm a bit busy right now, but I will take care of this and rework the patch in the next couple of weeks!

@akinomyoga
Copy link
Owner

@timjrd OK, thank you for letting me know that! Please take your time.


I have updates on substr filter for pathnames. In short, 1) I have cleaned up the filtering code in 7c6b67b (There is no conflict with this PR). 2) I won't support substr filter like *dir1*/*dir2*/*file* but just *dir1/dir2/file*. More flexible matching should be achieved in the subsequent hsubseq / subseq filter. I added the corresponding glob pattern specialized to substr for source:file (here).

If you are interested in the reasons, here is the detail.
  • In the first review comment, I wrote that the current ble.sh implementation of the filter is not clean, but now I cleaned up the codes in the latest commit 7c6b67b. Previously, the filter is performed on the candidate list after the source added all the candidates in the list. Now, the filter is performed in ble/complete/cand/yield which is called inside the source, so that the candidates that don't match the condition are not added in the list from the beginning.
  • I decided not to support the special filter of the form substr like *dir1*/*dir2*/*file* as your decision. The reason is that the filtering rule becomes inconsistent when several sources produce the candidates with different rules. When there are several viable candidates, the inserted text will be the common prefix or common subsequence, but when there are candidates of different filtering rules, the common subsequence rule becomes non-trivial. This is not only tricky to implement but also not user-friendly. So I will not support *dir1*/*dir2*/*file*.

@timjrd
Copy link
Contributor Author

timjrd commented Nov 29, 2020

Hi! Here is the patch reworked according to your comments.

In the meantime I switched to infinite Bash history and after some time auto-completion became very slow, I thought I could add a limit check inside the history source, especially since other users are experiencing issues with big histories. I also faced huge slowdowns with completion on paths containing wildcards. I will try to address these two issues when I have some time, but I need to reproduce them reliably first. Maybe this would be a better fit for another PR.

Concerning the visual bell, it's not a big deal, but do you think it would be possible to display the message limit reached in place of the completion candidates (below the prompt)?

Thanks!

@akinomyoga
Copy link
Owner

Nice! Thank you!

In the meantime I switched to infinite Bash history and after some time auto-completion became very slow, [...] Maybe this would be a better fit for another PR.

I think they can be discussed in separate PRs. Let's first settle this PR, and then you can make another PR/PRs.

after some time auto-completion became very slow, I thought I could add a limit check inside the history source

May I ask in detail? In what sense it is slow?

  • Does the response to the user input become slow? The auto-complete is designed not to block the user-input processing in the current implementations, so if auto-complete makes the response to the user input slow, that is the bug of the user input detection. So the fix should be made in that detection rather than by limiting the number of candidates.
  • Or, does the time until a suggestion become longer? In this case, canceling auto-complete is effectively equivalent to make the time infinite in the sense of slowness, so I don't think adding a limit check in the history source improves the user experience. Maybe canceling the history-based auto-complete early and falling to the normal word-by-word auto-complete would make the waiting time faster, but I actually don't want to break the reproducibility of the auto-complete results by unpredictable conditions. Though this does not affect the user experience, another problem might be again the CPU usage.

especially since other users are experiencing issues with big histories.

I actually doubt if his problem is really related to the big history. As already written, the history-based completion is designed not to block the user input, i.e., the processing will be immediately canceled when there is user input, so I don't have an idea what happened there. Then he didn't respond after that, so there was no chance to get a hint on the problem. Usually, the initial guess of users (who didn't really look into the code) is wrong (or, in other words, mostly random) to my experience. In particular, a large history is a popular naive guess in the past issues actually.

Re: Limit reached message

Concerning the visual bell, it's not a big deal, but do you think it would be possible to display the message limit reached in place of the completion candidates (below the prompt)?

Yes, it is possible. Actually, I was also thinking a similar idea. You can use the function ble-edit/info/show to put the message below the command line. For example, you can use the following command.

ble-edit/info/show ansi $'\e[38;5;242m(too many completions)\e[m'

But, here we need to carefully design the behavior because the limit check is performed for each source which means that, even when some source reached the limit, another source might still generate candidates. In that case, both the message and the completions cannot be shown in the same place at the same time.

For example, how about showing the message below the command line only when there are no completions at all, or otherwise ringing bells?

lib/core-complete.sh Outdated Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
lib/core-complete.sh Outdated Show resolved Hide resolved
@timjrd
Copy link
Contributor Author

timjrd commented Dec 6, 2020

Hi, thanks for your feedback!

May I ask in detail? In what sense it is slow?

I'm pretty sure that the slowdowns were caused by the Bash option HISTCONTROL=erasedups. But I can easily live without it. There is however a performance problem with completion on paths with glob patterns but I will open a dedicated issue for that.

For example, how about showing the message below the command line only when there are no completions at all, or otherwise ringing bells?

Sounds fine!

@akinomyoga
Copy link
Owner

the slowdowns were caused by the Bash option HISTCONTROL=erasedups

OK! I think this causes the slowdown when a command is executed and thus added to the command history, but does it cause the slowdown to auto-complete? I'm going to test it in detail.

There is however a performance problem with completion on paths with glob patterns but I will open a dedicated issue for that.

OK!

Sounds fine!

Thank you! Then, you can switch to ble-edit/info/show (as described in #65 (comment)) when cand_count == 0!

@timjrd
Copy link
Contributor Author

timjrd commented Dec 19, 2020

Hi! Here is the patch reworked with exit codes. I tried to use ble-edit/info/show, but cand_count is not always zero even if there are no candidates so I left the code with the bell as-is. Thanks.

@akinomyoga
Copy link
Owner

akinomyoga commented Dec 20, 2020

Thank you!

May I ask if you have diff between the previous patch and the current patch? Since the changes are squashed (or the commit is directly amended), it is cumbersome to track what has been updated since the previous patch. I would like to avoid reviewing the same code from the beginning many times. Of course, I can still create temporary two branches on a certain commit at my local repository, cherry-pick both the previous and current patches on each branch and perform diff to see what has been changed, but I'm lazy to do that every time. So, if you don't have specific reasons to squash the changes for every update of the PR, I'll be happy if, from the next time, commits for incremental changes are just added. (BTW, rebasing and conflict resolution are welcome, unlike the squashing.) We can squash all the commits just before the merge :)

Anyway this time I'll make diff at my side

Edit: Here is the diff.
diff --git a/lib/core-complete.sh b/lib/core-complete.sh
index 32b032a..e9f311c 100644
--- a/lib/core-complete.sh
+++ b/lib/core-complete.sh
@@ -1297,23 +1297,21 @@ function ble/complete/action:variable/init-menu-item {
 #------------------------------------------------------------------------------
 # source
 
-## Takes an array by reference as $1 and empty it if its size exceeds
-## $bleopt_complete_limit or $bleopt_complete_limit_auto.
+## Test whether $1 exceeds $bleopt_complete_limit or
+## $bleopt_complete_limit_auto.
 ##
-## ble/complete/source/limit my_array # no '$'
-##
-function ble/complete/source/limit {
-  declare -n ref=$1
-
+function ble/complete/source/test-limit {
   if [[ :$comp_type: == *:auto:* && $bleopt_complete_limit_auto ]]; then
     local limit=$bleopt_complete_limit_auto
   else
     local limit=$bleopt_complete_limit
   fi
 
-  if [[ $limit && ${#ref[@]} -gt $limit ]]; then
+  if [[ $limit && $1 -gt $limit ]]; then
     complete_limit_reached=1
-    ref=()
+    return 1
+  else
+    return 0
   fi
 }
 
@@ -1593,6 +1591,7 @@ function ble/complete/source:command/gen {
     local ret
     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
     ble/complete/util/eval-pathname-expansion "$ret/"
+    ble/complete/source/test-limit ${#ret[@]} || return 1
     ((${#ret[@]})) && printf '%s\n' "${ret[@]}"
   fi
 }
@@ -1626,7 +1625,7 @@ function ble/complete/source:command {
   [[ $compgen ]] || return 1
   ble/util/assign-array arr 'ble/bin/sort -u <<< "$compgen"' # 1 fork/exec
 
-  ble/complete/source/limit arr
+  ble/complete/source/test-limit ${#arr[@]} || return 1
 
   for cand in "${arr[@]}"; do
     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
@@ -1703,8 +1702,6 @@ function ble/complete/util/eval-pathname-expansion {
 
   ble/array#reverse dtor
   ble/util/invoke-hook dtor
-
-  ble/complete/source/limit ret
 }
 
 ## 関数 ble/complete/source:file/.construct-ambiguous-pathname-pattern path
@@ -1777,6 +1774,8 @@ function ble/complete/source:file/.impl {
   [[ :$opts: == *:directory:* ]] && ret=${ret%/}/
   ble/complete/util/eval-pathname-expansion "$ret"
 
+  ble/complete/source/test-limit ${#ret[@]} || return 1
+
   candidates=()
   local cand
   if [[ :$opts: == *:directory:* ]]; then
@@ -2396,7 +2395,7 @@ function ble/complete/progcomp/.compgen {
     fi
   } 2>/dev/null
 
-  ble/complete/source/limit arr
+  ble/complete/source/test-limit ${#arr[@]} || return 1
 
   local action=progcomp
   [[ $comp_opts == *:filenames:* && $COMPV == */* ]] && COMP_PREFIX=${COMPV%/*}/
@@ -2883,6 +2882,7 @@ function ble/complete/source:argument {
     local ret cand
     ble/complete/source:file/.construct-pathname-pattern "$value"
     ble/complete/util/eval-pathname-expansion "$ret"
+    ble/complete/source/test-limit ${#ret[@]} || return 1
     for cand in "${ret[@]}"; do
       [[ -e $cand || -h $cand ]] || continue
       [[ $FIGNORE ]] && ! ble/complete/.fignore/filter "$cand" && continue
@@ -2909,7 +2909,7 @@ function ble/complete/source/compgen {
   local arr
   ble/util/assign-array arr 'builtin compgen -A "$compgen_action" -- "$compv_quoted"'
 
-  ble/complete/source/limit arr
+  ble/complete/source/test-limit ${#arr[@]} || return 1
 
   # 既に完全一致している場合は、より前の起点から補完させるために省略
   [[ $1 != '=' && ${#arr[@]} == 1 && $arr == "$COMPV" ]] && return 0
@@ -6311,6 +6311,7 @@ function ble/cmdinfo/complete:cd/.impl {
     local ret cand
     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
     ble/complete/util/eval-pathname-expansion "$name$ret"
+    ble/complete/source/test-limit ${#ret[@]} || return 1
     for cand in "${ret[@]}"; do
       ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
         ble/complete/check-cancel && return 148
@@ -6339,6 +6340,7 @@ function ble/cmdinfo/complete:cd/.impl {
     local ret cand
     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
     ble/complete/util/eval-pathname-expansion "${ret%/}/"
+    ble/complete/source/test-limit ${#ret[@]} || return 1
     for cand in "${ret[@]}"; do
       ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
         ble/complete/check-cancel && return 148

@akinomyoga
Copy link
Owner

akinomyoga commented Dec 20, 2020

but cand_count is not always zero even if there are no candidates so I left the code with the bell as-is.

For example, cand_count == 0 can be reproduced with the following commandline edit:

$ bleopt complete_limit=5 complete_ambiguous=
$ h[TAB]

Now I tried to implement the original suggestion of showing messages in the next line, but the behavior seemed to be a bit unnatural, so now I think we don't have to support it. Here's the detail:

Now I tried the suggested behavior with the following change, but it seems the behavior is a bit unnatural in vim mode. Actually, the line below the commandline is used for various purposes including the completion menu, current editing modes (various maps in vim / MULTILINE in emacs), error messages, etc. If the completion sets the error message, this message (too many completions), which hides the other information, remains all along while the user inputs additional texts until the next completion, which is a bit strange.

diff --git a/lib/core-complete.sh b/lib/core-complete.sh
index e9f311c..c7d8931 100644
--- a/lib/core-complete.sh
+++ b/lib/core-complete.sh
@@ -4804,17 +4804,21 @@ function ble/widget/complete {
     ble/complete/generate-candidates-from-opts "$opts"; local ext=$?
     if ((ext==148)); then
       return 148
-    fi
+    elif ((ext!=0||cand_count==0)); then
       if [[ $complete_limit_reached ]]; then
+        ble-edit/info/show ansi $'\e[38;5;242m(too many completions)\e[m'
+      else
         [[ :$opts: != *:no-bell:* ]] &&
-        ble/widget/.bell 'complete: limit reached'
-    fi
-    if ((ext!=0||cand_count==0)); then
-      [[ :$opts: != *:no-bell:* && -z $complete_limit_reached ]] &&
           ble/widget/.bell 'complete: no completions'
         ble-edit/info/clear
+      fi
       return 1
     fi
+
+    if [[ $complete_limit_reached ]]; then
+      [[ :$opts: != *:no-bell:* ]] &&
+        ble/widget/.bell 'complete: limit reached'
+    fi
   fi

   if [[ :$opts: == *:insert_common:* ]]; then

So, now I think we don't have to support the special treatment using ble-edit/info/show. If you have a better idea, please feel free to let me know that!

@akinomyoga
Copy link
Owner

I think it's almost done! The remaining are just trivial fixes and cosmetic changes.

  • In the function ble/complete/auto-complete/.check-context,

    ble.sh/lib/core-complete.sh

    Lines 5361 to 5365 in 1a4f87e

    local COMP1 COMP2 COMPS COMPV
    local comps_flags comps_fixed
    local cand_count
    local -a cand_cand cand_word cand_pack
    ble/complete/candidates/generate; local ext=$?
    The local variable complete_limit_reached is leaked here, i.e., the global variable named complete_limit_reached would be created and changed while auto-complete. Can you add the following line just before the call of ble/complete/candidates/generate to correctly limit the scope inside ble.sh?

    local complete_limit_reached=
  • To make it clear that the variable complete_limit_reached can be modified in the function, can you add the following line in the function descriptions?

    ##   @var[in,out] complete_limit_reached

    The corresponding functions are:

    ble.sh/lib/core-complete.sh

    Lines 3549 to 3558 in 1a4f87e

    ## 関数 ble/complete/candidates/generate opts
    ## @param[in] opts
    ## @var[in] comp_text comp_index
    ## @arr[in] sources
    ## @var[out] COMP1 COMP2 COMPS COMPV
    ## @var[out] comp_type comps_flags comps_fixed
    ## @var[out] cand_*
    function ble/complete/candidates/generate {
    local opts=$1

    ble.sh/lib/core-complete.sh

    Lines 4070 to 4075 in 1a4f87e

    ## 関数 ble/complete/generate-candidates-from-opts opts
    ## @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
    ## @var[out] cand_count cand_cand cand_word cand_pack
    function ble/complete/generate-candidates-from-opts {
    local opts=$1

  • We can also add the same line to the following description.

    ble.sh/lib/core-complete.sh

    Lines 1420 to 1431 in 1a4f87e

    ## source の実装
    ##
    ## 関数 ble/complete/source:$name args...
    ## @param[in] args...
    ## ble/syntax/completion-context/generate で設定されるユーザ定義の引数。
    ##
    ## @var[in] COMP1 COMP2 COMPS COMPV comp_type
    ## @var[in] comp_filter_type
    ## @var[out] COMP_PREFIX
    ## ble/complete/cand/yield で参照される一時変数。
    ##

    Now I think we can also list other cand_* variables here, so can you add the following comments to the above code section?

    ##   @var[in,out] cand_count cand_cand cand_word cand_pack
    ##   @var[in,out] complete_limit_reached
  • I thought maybe it's better to rename complete_limit_reached to cand_limit_reached because the scope and usage of the variable is similar to the other cand_* variables. What do you think of this idea?

  • This is just a coding style, but in the following section, could you use [[ ! ..... ]] instead of [[ -z ..... ]]?

    ble.sh/lib/core-complete.sh

    Lines 4812 to 4815 in 1a4f87e

    if ((ext!=0||cand_count==0)); then
    [[ :$opts: != *:no-bell:* && -z $complete_limit_reached ]] &&
    ble/widget/.bell 'complete: no completions'
    ble-edit/info/clear

@akinomyoga
Copy link
Owner

Hi! How are you doing? It's not in a rush, but do you have a plan to finalize this PR? If you have already lost interest in this PR, I'm ready to continue finalizing the PR at my side (yet it seems the remaining fixes are rather trivial ones) :) Or, if you are just busy recently, I would appreciate it if you could just tell me that you are busy. Again, It's not in a rush, so I can wait as far as you don't lose interest! Thank you!

@timjrd
Copy link
Contributor Author

timjrd commented Jan 21, 2021 via email

@akinomyoga
Copy link
Owner

OK! Thank you for your quick response!

@timjrd
Copy link
Contributor Author

timjrd commented Jan 24, 2021

Hi :) Here are the remaining fixes. Thanks!

@akinomyoga
Copy link
Owner

Thank you! Looks fine! I merge it.

@akinomyoga akinomyoga merged commit b13f114 into akinomyoga:master Jan 25, 2021
@akinomyoga
Copy link
Owner

akinomyoga commented Jan 25, 2021

Thank you so much for all the discussion and your work! I have merged the commit. I closed Issue #64 with this PR. If you think we need further discussion in #64 for another possible PR, please feel free to reopen it. I also added related descriptions to the manual (bleopt complete_limit and bleopt complete_limit_auto).

If you are willing in the future to discuss another performance improvement that you have mentioned in the following comment, you are always welcome!

#65 (comment)

In the meantime I switched to infinite Bash history and after some time auto-completion became very slow, I thought I could add a limit check inside the history source, especially since other users are experiencing issues with big histories. I also faced huge slowdowns with completion on paths containing wildcards. I will try to address these two issues when I have some time, but I need to reproduce them reliably first. Maybe this would be a better fit for another PR.

Thanks again for your work!

@timjrd
Copy link
Contributor Author

timjrd commented Jan 25, 2021

I hope this tiny feature will prove useful :). Thanks for your time and for your help!

@akinomyoga
Copy link
Owner

Hi @timjrd. Recently, based on detailed reports in #82 by @3ximus, I have been adding a number of optimizations to completion and highlighting, which I think have some overlaps with your contributions. Today I pushed them to the master branch.

Now I think the user experience became much more responsive than before, so I'm thinking of increasing the default limit values for complete_limit{,_auto} that you introduced. If you have any thoughts, please let me know! (Actually, I'm not sure if these recent optimizations improve the performance issue you had. If not, maybe the default limit values should be kept as is.) Thank you!

@timjrd
Copy link
Contributor Author

timjrd commented Mar 6, 2021

Hi! Sorry for the delay. I just tested revision aae553c (current master, with complete_limit set to infinity). In my case Bash is still frozen for a couple of seconds when attempting tab-completion on a very large directory.

@timjrd
Copy link
Contributor Author

timjrd commented Mar 6, 2021

I tried to re-enable complete_limit but it doesn't seem to work anymore. If you would like to reproduce my issue, you could create say 20k empty directories + 20k empty files under the same directory and attempt a tab-completion on it. I must acknowledge that it's a quite unusual use case, so I can totally understand the slowdown.

(unrelated, but this latest version doesn't work with the Kitty terminal emulator (seems to work fine with gnome-terminal though))

@akinomyoga
Copy link
Owner

Thank you for the feedback!

In my case Bash is still frozen for a couple of seconds

Does it mean the shell doesn't respond soon when the user supplies additional inputs while TAB completion? If the user doesn't input anything while the TAB completion, ble.sh continues the processing which is the intended behavior.

@akinomyoga
Copy link
Owner

If you would like to reproduce my issue, you could create say 20k empty directories + 20k empty files under the same directory and attempt a tab-completion on it.

OK, I could reproduce it. By the way, what is your operating system? I reproduced it with Linux (Fedora).

(unrelated, but this latest version doesn't work with the Kitty terminal emulator (seems to work fine with gnome-terminal though))

Thank you for the report. It seems there is a Linux version of Kitty, so I think I'll try it later.

@timjrd
Copy link
Contributor Author

timjrd commented Mar 6, 2021

Does it mean the shell doesn't respond soon when the user supplies additional inputs while TAB completion? If the user doesn't input anything while the TAB completion, ble.sh continues the processing which is the intended behavior.

For my use case, that's mostly the same problem as before: when I attempt a tab-completion a my very large directory, Bash is maxing up one CPU core for 10+ seconds. When I want to type something, the completion is correctly interrupted, but sometimes it takes a few seconds to respond.

OK, I could reproduce it. By the way, what is your operating system? I reproduced it with Linux (Fedora).

I'm using Linux too, with an ext4 filesystem, and Bash 5. I'm using the NixOS distribution which is why I have to deal with a huge directory (the Nix store, ~40k entries on my system).

@akinomyoga
Copy link
Owner

akinomyoga commented Mar 7, 2021

In the previous commit, I wrote I could reproduce it, but possibly it was a different issue...

In my case, the problem has occurred in some loops inside an old version of bash-completion. In the loop, bash-completion uses the command read which is overwritten by ble.sh. ble.sh overwrites read to emulate read -e because builtin read -e doesn't work when ble.sh is enabled and readline is disabled. This ble.sh version of read is slower than the original Bash builtin which produced a visible difference on huge directories. However, these loops in _filedir were removed by the pull request scop/bash-completion#205 on 2018-04. There are still a few similar loops in _filedir_xspec. For this issue, I'll later add workarounds.

May I ask questions to check whether your performance issue is related to the above one or not?

  • Q1. Does it still reproduce after running complete -r? (Note: complete -r removes all the programmable completion settings in the current session).
  • Q2. If the answer to Q1 is yes, what is the version of your bash-completion? The version of bash-completion is stored in the array BASH_COMPLETION_VERSINFO. You can check it by declare -p BASH_COMPLETION_VERSINFO.
  • Q3. Does the performance issue occur with common commands such as the echo command or occur only with some special commands?

@akinomyoga
Copy link
Owner

akinomyoga commented Mar 7, 2021

I pushed some fixes to master.

In commit 856cec2, I fixed the issue I mentioned in the previous reply. To stop the loops (while read ...) inside bash-completion, I hooked read to check user inputs and complete_limit_{,auto}.

(unrelated, but this latest version doesn't work with the Kitty terminal emulator (seems to work fine with gnome-terminal though))

In commit eca2976, I slightly changed an escape sequence so that Kitty can recognize it. Kitty doesn't recognize DECSTBM (CSI line ; col r) when line and col are empty. I switched to use CSI r which Kitty seems to understand (Actually, the behavior of Kitty is often strange).

@timjrd
Copy link
Contributor Author

timjrd commented Mar 13, 2021

Hi! Thanks a lot for these fixes!

The following tests were done on master (c8e658e):

Q1. Does it still reproduce after running complete -r? (Note: complete -r removes all the programmable completion settings in the current session).

Yes. Same thing.

Q2. If the answer to Q1 is yes, what is the version of your bash-completion? The version of bash-completion is stored in the array BASH_COMPLETION_VERSINFO. You can check it by declare -p BASH_COMPLETION_VERSINFO.

$ declare -p BASH_COMPLETION_VERSINFO
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="10")

Q3. Does the performance issue occur with common commands such as the echo command or occur only with some special commands?

Same thing with echo.

@akinomyoga
Copy link
Owner

OK! Thank you for testing! There seems to be still the performance issue even with the latest version in your environment. Actually, now I cannot reproduce the problem in my environment. For example, it works smoothly with the following command line:

$ mkdir {00000..19999}
$ touch {00000..19999}.txt
$ echo 012345
  • I'm using two Fedora 30 systems where the main filesystem is XFS. I also tried Ubuntu 18 in VirtualBox where the filesystem is ext4. In these environments, I don't see a significant delay in quick testing.
  • I also tried Arch Linux in VirtualBox where the filesystem is ext4. In this system, I observed a slight delay (about 0.5s in my feeling) in the working directory with 100k files and 100k directories.

You have told me that it sometimes takes a few seconds (as quoted below), but can you tell roughly how often it happens?

When I want to type something, the completion is correctly interrupted, but sometimes it takes a few seconds to respond.

Have you experienced the delay in more than one NixOS system? If it consistently reproduces in multiple NixOS hosts, maybe I can set up my own NixOS distribution in my VirtualBox.

@timjrd
Copy link
Contributor Author

timjrd commented Mar 13, 2021

Did you try:

$ mkdir {00000..19999}
$ touch {00000..19999}.txt
$ echo <TAB-TAB>

Without any prefix?

@akinomyoga
Copy link
Owner

OK, thank you for the information. I could reproduce it with 100k files + 100k directories and a certain interval between <TAB-TAB> and a user interruption. I'll later take a look.

@timjrd
Copy link
Contributor Author

timjrd commented Mar 13, 2021

I guess you have a NVMe SSD and a beefier CPU, or maybe XFS is doing a better job here xD

@akinomyoga
Copy link
Owner

akinomyoga commented Mar 15, 2021

I found one bottleneck, but there are also several suspicious places. The processing speed of these suspicious places are not so bad in my environment, but they might be slow in your environment. Could you provide me the result of the following commands in normal Bash (i.e., with ble.sh detached) in your environment?

$ cd /path/to/big-directory
$ time files=(*)
$ time def=$(declare -p files)
$ time def=ret=${def#*=}
$ time eval "$def"
$ echo ${#ret[@]}
$ time def="ret=(${files[*]@Q})"

@timjrd
Copy link
Contributor Author

timjrd commented Mar 17, 2021

Here you go:

$ echo $BASH_VERSION
5.0.17(1)-release

$ time files=(*)
real 0m0.063s
user 0m0.042s
sys 0m0.020s

$ time def=$(declare -p files)
real 0m0.046s
user 0m0.031s
sys 0m0.017s

$ time def=ret=${def#*=}
real 0m0.026s
user 0m0.018s
sys 0m0.009s

$ time eval "$def"
real 0m0.879s
user 0m0.845s
sys 0m0.034s

$ echo ${#ret[@]}
33800

$ time def="ret=(${files[*]@Q})"
real 0m0.093s
user 0m0.082s
sys 0m0.011s

@akinomyoga
Copy link
Owner

@timjrd Thank you for your results, and sorry for the long blank. After various experiments, I finally added another performance improvement in c89aa23.

Thanks to your measurement, the following part turned out to have a significant delay (which was actually not so slow in my system with 100k elements).

$ time eval "$def"
real 0m0.879s
user 0m0.845s
sys 0m0.034s

A similar operation has been used in ble.sh to transfer the filename list from a subshell to the parent shell. I was thinking about a more efficient way to transfer a very large filename list. I tried several different implementations (tested with 500k-element arrays) and finally chose to parse the result of declare -p files using awk in the subshell and load the results using mapfile in the parent shell.

Hope it could improve the situation in

$ mkdir {00000..19999}
$ touch {00000..19999}.txt
$ echo <TAB-TAB>

Without any prefix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NYI/NewFeat Not yet implemented or New Feature performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pre-PR: [auto] completion performance improvements
2 participants