I’ve noticed that starting a new fish shell takes at least 2 seconds, and it’s really starting to annoy me.
Profiling
I decided to profile my config.fish script. Since I couldn’t find any profilers, I resorted to the oldest method: putting print and time statements everywhere in my fish script. I quickly found that the main bottleneck was the section initializing my env tools, so I wrapped it in the time command to measure the execution time.
time pyenv init - | source
# _______________________________________________________
# Executed in 264.81 millis fish external
# usr time 80.20 millis 1.47 millis 78.73 millis
# sys time 140.85 millis 1.82 millis 139.04 millis
time goenv init - | source
# _______________________________________________________
# Executed in 2.96 secs fish external
# usr time 0.70 secs 1.65 millis 0.70 secs
# sys time 1.83 secs 2.09 millis 1.83 secs
time direnv hook fish | source
# _______________________________________________________
# Executed in 15.11 millis fish external
# usr time 2.68 millis 83.00 micros 2.60 millis
# sys time 5.30 millis 172.00 micros 5.13 millis
time direnv export fish | source
# _______________________________________________________
# Executed in 11.63 millis fish external
# usr time 2.64 millis 25.00 micros 2.62 millis
# sys time 3.57 millis 360.00 micros 3.21 millis
time rbenv init - --no-rehash fish | source
# _______________________________________________________
# Executed in 51.44 millis fish external
# usr time 16.64 millis 217.00 micros 16.42 millis
# sys time 29.18 millis 387.00 micros 28.79 millis
What the hell: goenv takes about 3 seconds to start. I’m not sure what it’s doing, but it’s too long to continue using it. By the way, pyenv also doesn’t look great - 0.3 seconds.
Searching for alternatives
After researching alternatives to goenv, I came across asdf and mise, which caught my attention. These are tool version managers that can install and switch between multiple versions of different languages and tools. They can replace not only goenv, but all env tools.
A quick comparison showed that mise is a modern alternative to asdf, written in Rust, and it seems to have much better performance. At least, that’s what the developers claim. Their documentation even has a section describing how to install Rosetta versions of tools, so they sold me on it.
Integration
Step by step, I replaced goenv, pyenv, direnv, and rbenv with mise. The process was very simple:
- install
mise:brew install mise - delete initialization lines from
config.fishof all *env tools - put
mise activate fish | sourcein theconfig.fishfile
That’s it. After that, just go to your project folder and type: mise install and you will be happy.
Conclusion
I cleaned up my fish startup script by replacing all these tools with one line that takes only 0.05 seconds to execute. And completely moved to mise, as it’s a drop-in replacement for all *env tools. It even detects and respects .<language>-version files, and it just works seamlessly.
time mise activate fish | source
# ________________________________________________________
# Executed in 44.73 millis fish external
# usr time 8.40 millis 0.61 millis 7.79 millis
# sys time 10.83 millis 1.53 millis 9.30 millis
I can’t even recall a case when one tool replaced a bunch of others and did it in a much more intuitive and faster way. Really great job!