virtualfish
virtualfish copied to clipboard
Shell startup is slow due to VirtualFish completions
- [x] I am using Fish shell version 3.1 or higher.
- [x] I am using Python version 3.6 or higher.
- [x] I have searched the issues (including closed ones) and believe that this is not a duplicate.
- [x] If related to a plugin, I prefixed the issue title with the name of the plugin.
- OS version and name: Ubuntu 22.04
- Fish shell version: 3.7.1
- VirtualFish version: 2.5.6
Issue
I've been profiling my Fish shell startup time and have found the VirtualFish loading script to consume a significant chunk of time.
# Without "virtualfish-loader.fish" installed
$ time fish -c exit
________________________________________________________
Executed in 38.06 millis fish external
usr time 30.62 millis 0.00 micros 30.62 millis
sys time 8.52 millis 734.00 micros 7.79 millis
# With "virtualfish-loader.fish" installed
$ time fish -c exit
________________________________________________________
Executed in 188.56 millis fish external
usr time 152.15 millis 727.00 micros 151.42 millis
sys time 92.58 millis 313.00 micros 92.27 millis
After doing some investigation, I've found it to be directly caused by the __vfsupport_setup_autocomplete
function, specifically the part that extracts the help text from all the VF functions.
Here's timing information with an without that specific function:
$ time source virtualfish-loader.fish
time source virtualfish-loader.fish
________________________________________________________
Executed in 150.11 millis fish external
usr time 112.20 millis 16.33 millis 95.86 millis
sys time 91.53 millis 15.33 millis 76.20 millis
# Removed "emit virtualfish_did_setup_plugins" so the function is not called in virtual.fish
$ time source virtualfish-loader.fish
________________________________________________________
Executed in 10.14 millis fish external
usr time 8.00 millis 3.86 millis 4.14 millis
sys time 2.25 millis 0.00 millis 2.25 millis
To further investigate, I've extracted that specific function to its own script and profiled it as follows (repeated parts reduced for clarity):
fish --profile=/tmp/vf-completion vfsupport_setup_autocomplete.fish
Time Sum Command
7 7 > function __vfsupport_setup_autocomplete ...
26 111802 > __vfsupport_setup_autocomplete
2 2 -> function __vfcompletion_needs_command...
1 1 -> function __vfcompletion_using_command...
263 111685 -> for sc in (functions -a | sed -n '/__vf_/{s///g;p;}')...
3396 3396 --> functions -a | sed -n '/__vf_/{s///g;p;}'
246 10358 --> set -l helptext (functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|")
1192 10112 ---> functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|"
739 4739 ----> command /usr/bin/grep --color=auto $argv
69 69 --> complete -x -c vf -n '__vfcompletion_needs_command' -a $sc -d $helptext
365 5952 --> set -l helptext (functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|")
978 5587 ---> functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|"
4609 4609 ----> command /usr/bin/grep --color=auto $argv
65 65 --> complete -x -c vf -n '__vfcompletion_needs_command' -a $sc -d $helptext
120 5830 --> set -l helptext (functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|")
879 5710 ---> functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|"
4831 4831 ----> command /usr/bin/grep --color=auto $argv
(...repeated 10+ times...)
8 48 --> complete -x -c vf -n '__vfcompletion_needs_command' -a $sc -d $helptext
32 32 -> complete -x -c vf -n '__vfcompletion_using_command activate' -a "(vf ls)"
21 21 -> complete -x -c vf -n '__vfcompletion_using_command connect' -a "(vf ls)"
17 17 -> complete -x -c vf -n '__vfcompletion_using_command rm' -a "(vf ls)"
18 18 -> complete -x -c vf -n '__vfcompletion_using_command upgrade' -a "(vf ls)"
23 70 > __vfsupport_remove_env_on_deactivate_or_exit PROCESS_EXIT 436514 0
3 47 -> if begin; set -q _VF_TEMPORARY_ENV; and [ $_VF_TEMPORARY_ENV = (basename $VIRTUAL_ENV) ]; end...
32 44 --> begin...
12 12 ---> set -q _VF_TEMPORARY_ENV
As you can see, the for sc in (functions ...)
loop is taking the majority of the time (>100ms), and this is almost entirely attributed to every invokation of grep taking ~5ms.
Possible Solutions
Since the completions don't often change, it might be better to generate them once and cache them. Here's a working example that can replace the __vfsupport_setup_autocomplete
function in virtual.fish:
function __vfsupport_initialize_autocomplete
# Source the completion file if it exists.
set -q XDG_CACHE_HOME; or set XDG_CACHE_HOME $HOME/.cache
if test -f $XDG_CACHE_HOME/fish/virtualfish_complete.fish
source $XDG_CACHE_HOME/fish/virtualfish_complete.fish
else
# If not, create it and source it.
mkdir -p $XDG_CACHE_HOME/fish
# add completion for subcommands
for sc in (functions -a | sed -n '/__vf_/{s///g;p;}')
set -l helptext (functions "__vf_$sc" | grep -m1 "^function" | sed -E "s|.*'(.*)'.*|\1|")
echo complete -x -c vf -n '__vfcompletion_needs_command' -a \'$sc\' -d \'$helptext\' >> $XDG_CACHE_HOME/fish/virtualfish_complete.fish
end
echo 'complete -x -c vf -n \'__vfcompletion_using_command activate\' -a "(vf ls)"' >> $XDG_CACHE_HOME/fish/virtualfish_complete.fish
echo 'complete -x -c vf -n \'__vfcompletion_using_command connect\' -a "(vf ls)"' >> $XDG_CACHE_HOME/fish/virtualfish_complete.fish
echo 'complete -x -c vf -n \'__vfcompletion_using_command rm\' -a "(vf ls)"' >> $XDG_CACHE_HOME/fish/virtualfish_complete.fish
echo 'complete -x -c vf -n \'__vfcompletion_using_command upgrade\' -a "(vf ls)"' >> $XDG_CACHE_HOME/fish/virtualfish_complete.fish
source $XDG_CACHE_HOME/fish/virtualfish_complete.fish
end
end
function __vfsupport_setup_autocomplete
function __vfcompletion_needs_command
set cmd (commandline -opc)
if test (count $cmd) -eq 1 -a $cmd[1] = 'vf'
return 0
end
return 1
end
function __vfcompletion_using_command
set cmd (commandline -opc)
if test (count $cmd) -gt 1
if test $argv[1] = $cmd[2]
return 0
end
end
return 1
end
__vfsupport_initialize_autocomplete
end
This function takes ~120ms upon the first run, but subsequent runs take <2ms, representing a significant speedup!
Many thanks for the detailed report, Anthony. It is rare and refreshing to see not only a detailed report, but also some concrete ideas and code regarding how the issue might be resolved. Bravo!
As you accurately point out, VirtualFish completions rarely change, which makes me wonder whether we need to generate the completions (even in cached form) on every shell startup. Perhaps we could install the completions as part of the vf install
process? Maybe with a separate vf install --completions
flag that only generates the completions, to be used to update completions in case they have changed?
After reviewing where to put completions, perhaps they should go in ~/.local/share/fish/vendor_completions.d
?
Looking forward to your thoughts. Once again, much appreciated!