Skip to content

Shell startup is slow due to VirtualFish completions #243

@MooseV2

Description

@MooseV2
  • I am using Fish shell version 3.1 or higher.
  • I am using Python version 3.6 or higher.
  • I have searched the issues (including closed ones) and believe that this is not a duplicate.
  • 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions