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

nvm.sh is slow (200ms+) #539

Closed
dy-dx opened this issue Sep 30, 2014 · 51 comments
Closed

nvm.sh is slow (200ms+) #539

dy-dx opened this issue Sep 30, 2014 · 51 comments
Labels
performance This relates to anything regarding the speed of using nvm.

Comments

@dy-dx
Copy link

dy-dx commented Sep 30, 2014

On my system (OSX 10.9.5 & bash 4.3.27), nvm adds a lot of time to my .bash_profile:

$ time source ~/.nvm/nvm.sh

real    0m0.218s
user    0m0.107s
sys 0m0.163s

--This is much slower than rvm--:

$ time source ~/.rvm/scripts/rvm

real    0m0.011s
user    0m0.005s
sys 0m0.006s

I do not use .nvmrc files.

@ljharb
Copy link
Member

ljharb commented Sep 30, 2014

#337 would alleviate this by requiring much less code to be sourced.

If someone were to profile the script and figure out which parts were slowest, I'd be happy to try to speed them up. PRs are also welcome :-)

@dy-dx
Copy link
Author

dy-dx commented Oct 3, 2014

@ljharb My apologies -- I just realized that RVM takes just about as long as NVM when sourcing it for the first time. It exports a rvm_loaded_flag so it get skipped upon subsequent loading

@ljharb ljharb closed this as completed Oct 3, 2014
@maxpoletaev
Copy link

In zsh I use lazy loading for source files.
This is not a solution but shell runs faster.

lazy_source () {
    eval "$1 () { [ -f $2 ] && source $2 && $1 \$@ }"
}

NVM_SOURCE=$HOME/.nvm/nvm.sh
lazy_source nvm $NVM_SOURCE

@ljharb
Copy link
Member

ljharb commented Jun 10, 2015

not a bad idea. It could even be more specific as in:

nvm() { . "$NVM_DIR/nvm.sh" ; nvm $@ ; }

However, that wouldn't detect nvmrc or a default nvm alias and use them :-/

@ljharb ljharb added the performance This relates to anything regarding the speed of using nvm. label Aug 14, 2015
@daneroo
Copy link

daneroo commented Nov 25, 2015

Same here... still looking for a solution..

 $ time source ~/.nvm/nvm.sh 
 0.799s

@bgerm
Copy link

bgerm commented Dec 3, 2015

This is pretty slow:

NVM_NPM_PREFIX="$(npm config get prefix)"
$ time npm config get prefix
npm config get prefix  0.23s user 0.03s system 103% cpu 0.245 total

This line is about 0.03s and gets called twice:

VERSION="$(nvm_ls "$PATTERN" | command tail -n1)"

This line is also about 0.03s:

VERSION="$(nvm_match_version "$PROVIDED_VERSION")"

@ljharb
Copy link
Member

ljharb commented Dec 3, 2015

Any time npm is called, it's slow, sadly.

However, I think the risks of people unknowingly having a prefix set are more important than a small delay opening the shell. I'll continue working on speeding things up.

@bgerm
Copy link

bgerm commented Dec 3, 2015

Thanks, @ljharb. The delay was more of an observation than an annoyance.

@dreyks
Copy link

dreyks commented Jul 25, 2016

$ time source ~/.nvm/nvm.sh

real    0m3.163s
user    0m1.504s
sys     0m1.172s

clean install of nvm, no .nvmrc.

what could be the reason?

@ljharb
Copy link
Member

ljharb commented Jul 25, 2016

@dreyks Likely because npm config get prefix is slow. There's no way around that yet.

@dreyks
Copy link

dreyks commented Jul 25, 2016

@ljharb this still leaves about 2.5 seconds for the rest

$ time npm config get prefix
/home/user/.nvm/versions/node/v6.3.1

real    0m0.753s
user    0m0.564s
sys     0m0.236s

this isn't super critical, cause it happens only when I open another shell, but still I wonder if my setup is wrong

@ljharb
Copy link
Member

ljharb commented Jul 25, 2016

@dreyks if you try nvm unalias default, does it still happen?

@dreyks
Copy link

dreyks commented Jul 26, 2016

@ljharb

$ nvm unalias default
Deleted alias default - restore it with `nvm alias "default" "node"`
$ time source ~/.nvm/nvm.sh

real    0m0.123s
user    0m0.052s
sys     0m0.048s

@ljharb
Copy link
Member

ljharb commented Aug 2, 2016

When I have either a default alias, or an .nvmrc file, or both, it's slow. When I have neither, it's fast.

I think the best solution is to add --no-use to your profile file - ie, . "$NVM_DIR/nvm.sh" --no-use. Does that solve the problem for whoever's subscribed to this issue?

@ljharb
Copy link
Member

ljharb commented Aug 2, 2016

  • 0.400s of the 0.900s execution time i'm running into is from nvm_die_on_prefix, which can't be avoided when doing nvm use.
  • 0.050s of it is from nvm_ensure_version_installed
  • 0.070s is from after nvm_ensure_version_installed
  • 0.030s comes from the argument-parsing case statement
  • 0.110s from the nvm use command (up to the argument-parsing case statement)
  • 0.470s comes from nvm_match_version in nvm_auto
  • 0.023s for the parsing up til then.

(Obviously these times are specific to my machine, but the relative sizes are what's important).

In other words, when no default/nvmrc is defined, it can skip 0.870s of the startup time. The only way I could make this fast by default is to remove the auto-using behavior. However, for those who need the speed, they can add --no-use to the sourcing command, obviating the problem.

ljharb added a commit that referenced this issue Aug 2, 2016
@dreyks
Copy link

dreyks commented Aug 2, 2016

the --no-use solves the problem indeed, thanks

@parasyte
Copy link

parasyte commented Sep 9, 2016

For anyone who comes across this in the future;

nvm kills my productivity because bash takes two orders of magnitude longer to create a new shell when a default alias exists. As a workaround, I added this hack to my .bash_profile after nvm.sh is loaded:

# nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" --no-use # This loads nvm

alias node='unalias node ; unalias npm ; nvm use default ; node $@'
alias npm='unalias node ; unalias npm ; nvm use default ; npm $@'

Delete the default alias and .nvmrc; Shell creation is fast again, and I still have a default node through the alias hack. First run of node or npm has the startup penalty, but that's a far better compromise than typing nvm use v4.4.7 every time I want to start a REPL.

@ljharb
Copy link
Member

ljharb commented Sep 9, 2016

@parasyte as has been said in this thread and others, add --no-use to the end of your profile line that sources nvm.sh and it should avoid the attempt to use the default alias.

@parasyte
Copy link

parasyte commented Sep 9, 2016

@ljharb That's all fine and good, but really inconvenient if you still want to use node.

@ljharb
Copy link
Member

ljharb commented Sep 9, 2016

@parasyte understood; and your alias is fine too - i'm mostly pointing out that you don't need to remove your default alias, and you can do alias node='nvm use; unalias node; node $@'.

@parasyte
Copy link

parasyte commented Sep 9, 2016

Gotcha. That is a nice little enhancement. I misunderstood that you were providing a tip. 😃

I guess I'll have to keep the alias, and stay hopeful that one day the startup time will get better! Cheers.

@gitaaron
Copy link

I feel like I must have been doing something wrong, however, to get around some circular logic entering an infinite loop I had to make the unalias the first step.

alias node='unalias npm; unalias node; nvm_init; use_node_version; node $@'
alias npm='unalias node; unalias npm; nvm_init; use_node_version; npm $@'

function nvm_init {
    export NVM_DIR="/Users/asurty/.nvm"
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" --no-use  # This loads nvm
}

function use_node_version {
   local node_version="$(nvm version)"
   local nvmrc_path="$(nvm_find_nvmrc)"

   
    FILE=.nvmrc
    if [ "$nvmrc_node_version" != "$node_version" ]; then
        nvm use
    fi
}

Thanks

@gitaaron
Copy link

Also, if I don't have an .nvmrc and I run 'node' then I get a long message from nvm. Is there a best practice to suppress that?

@ljharb
Copy link
Member

ljharb commented Feb 23, 2017

nvm alias default node?

@ns-cweber
Copy link

ns-cweber commented Apr 27, 2017

Here's my data point (Macbook Pro, OS X 10.11.6):

Documents $ time source /usr/local/opt/nvm/nvm.sh

real    0m3.964s
user    0m0.181s
sys     0m0.309s

Documents $ nvm version
v0.12.18

This does not change based on whether I source it or run it.

@ljharb
Copy link
Member

ljharb commented Apr 27, 2017

@ns-cweber why is nvm.sh in /usr/local/opt? did you perhaps install it with homebrew?

@ns-cweber
Copy link

@ljharb That's correct.

@ljharb
Copy link
Member

ljharb commented Apr 28, 2017

@ns-cweber homebrew installation of nvm is unsupported, and often runs much slower than a normal installation (curling the install script in the readme). Can you try brew uninstalling it, and installing it the recommended way?

@ns-cweber
Copy link

ns-cweber commented May 1, 2017

I uninstalled the brew version and reinstalled via curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash. It looks like it put nvm.sh in ~/.nvm/nvm.sh (echo $NVM_DIR prints $HOME/.nvm). It now takes ~4.6 seconds:

$ time source ~/.nvm/nvm.sh

real    0m4.645s
user    0m0.576s
sys     0m0.356s

@ljharb
Copy link
Member

ljharb commented May 1, 2017

Thanks, that's definitely correct. What does nvm debug print out?

@ns-cweber
Copy link

nvm --version: v0.33.2
$SHELL: /usr/local/bin/bash
$HOME: /Users/cweber
$NVM_DIR: '$HOME/.nvm'
$PREFIX: ''
$NPM_CONFIG_PREFIX: ''
$NVM_NODEJS_ORG_MIRROR: ''
$NVM_IOJS_ORG_MIRROR: ''
shell version: 'GNU bash, version 4.4.0(1)-release (x86_64-apple-darwin15.6.0)'
uname -a: 'Darwin 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64'
OS version: Mac 10.11.6 15G31
curl: /usr/bin/curl, curl 7.43.0 (x86_64-apple-darwin15.0) libcurl/7.43.0 SecureTransport zlib/1.2.5
wget: not found
git: /usr/local/bin/git, git version 2.6.1
nvm current: v0.12.18
which node: $NVM_DIR/versions/node/v0.12.18/bin/node
which iojs:
which npm: $NVM_DIR/versions/node/v0.12.18/bin/npm
npm config get prefix: $NVM_DIR/versions/node/v0.12.18
npm root -g: $NVM_DIR/versions/node/v0.12.18/lib/node_modules

@ljharb
Copy link
Member

ljharb commented May 1, 2017

That also looks normal :-/ I'm quite sure you can add --no-use to the sourcing command and it will go faster - and it's possible that if your default was a version of node with a much newer version of npm, it would go faster - but it's likely that the npm config get prefix call is what's slowing you (and many others) down.

@ns-cweber
Copy link

You're right:

temp $ time source ~/.nvm/nvm.sh --no-use

real    0m0.112s
user    0m0.018s
sys     0m0.018s

Unless I'm doing something wrong, varying the version of Node doesn't significantly impact performance.

@medisoft
Copy link

medisoft commented Jul 22, 2017

How about this?

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use  # This loads nvm
export PATH=$HOME/.nvm/versions/node/v$(cat $HOME/.nvm/alias/default)/bin:$PATH

What could be missing with this code?

@ljharb
Copy link
Member

ljharb commented Jul 22, 2017

@medisoft
If you want to be closer to nvm's internal code, you could use:

export PATH="$(nvm_prepend_path "$PATH" "$(nvm_version_path "$(nvm_alias default)")/bin")"

That still fails to set the MANPATH but that's pretty close.

@codfish
Copy link

codfish commented Sep 12, 2017

@parasyte 👍 nice workaround here #539 (comment), definitely saves a load of time. Only small annoyance is in the scenario where you start a new session and then want to run a node package cli, i.e. gulp, and you can't cause it's not in the PATH yet. I mean, you could obviously go nuts with aliases, but you'd have to add a new one each time you installed a new node package with a cli, which isn't ideal. Just wondering if you've personally come up with a solution for yourself or have you just gotten used to running npm first, then being able to run other commands?

@pk-nb
Copy link

pk-nb commented Oct 5, 2017

For people who want to respect the .nvmrc without thinking, and still preserve the lazy loading so that shell boot and cd are still fast in zsh, I managed to put together a nice alias method off of @parasyte's method.

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" --no-use # This setups nvm to be lazy-loaded

load-nvmrc() {
  local node_version="$(nvm version)"
  local nvmrc_path="$(nvm_find_nvmrc)"

  if [ -n "$nvmrc_path" ]; then
    local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")

    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install
    elif [ "$nvmrc_node_version" != "$node_version" ]; then
      nvm use
    fi
  elif [ "$node_version" != "$(nvm version default)" ]; then
    echo "Reverting to nvm default version"
    nvm use default
  fi
}

# Alias node, npm, and yarn to access node lazily. We reset the alias on cd to ensure we always use
# the right nvm version when in a new folder (respecting the nvmrc if possible).
alias node='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; node $@';
alias npm='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; npm $@';
alias yarn='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; yarn $@';

unset_nvm() {
  if [[ "$NVM_LOADED_FOR_PATH" -eq 1 ]]; then
    export NVM_LOADED_FOR_PATH=0
    alias node='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; node $@';
    alias npm='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; npm $@';
    alias yarn='unalias node ; unalias npm ; unalias yarn ; export NVM_LOADED_FOR_PATH=1 ; load-nvmrc ; yarn $@';
  fi
}

add-zsh-hook chpwd unset_nvm

The nice thing is that this allows the system node and yarn to still work (as other home-brew dependencies often install them). It works by resetting the alias on cdso that the next time you run, it will check to make sure your version is correct (rather than running on cd, which is slow and too optimistic).

@ljharb
Copy link
Member

ljharb commented Oct 5, 2017

Note that this still does not allow globally-installed packages in your desired node version to work.

@parasyte
Copy link

parasyte commented Oct 5, 2017

For that and other reasons, I gave up and switched to managing node with brew. It's ugly, but a few aliases will take care of that, surely.

jay@JayMBP:~$ readlink $(which node)
../Cellar/node@6/6.11.3/bin/node
jay@JayMBP:~$ node --version
v6.11.3

jay@JayMBP:~$ brew unlink node@6 && brew link --force node@4
Unlinking /usr/local/Cellar/node@6/6.11.3... 7 symlinks removed
Linking /usr/local/Cellar/node@4/4.8.4_1... 7 symlinks created

If you need to have this software first in your PATH instead consider running:
  echo 'export PATH="/usr/local/opt/node@4/bin:$PATH"' >> ~/.bash_profile
jay@JayMBP:~$ readlink $(which node)
../Cellar/node@4/4.8.4_1/bin/node
jay@JayMBP:~$ node --version
v4.8.4

jay@JayMBP:~$ brew unlink node@4 && brew link --force node
Unlinking /usr/local/Cellar/node@4/4.8.4_1... 7 symlinks removed
Linking /usr/local/Cellar/node/8.6.0... 7 symlinks created
jay@JayMBP:~$ readlink $(which node)
../Cellar/node/8.6.0/bin/node
jay@JayMBP:~$ node --version
v8.6.0

jay@JayMBP:~$ brew unlink node && brew link --force node@6
Unlinking /usr/local/Cellar/node/8.6.0... 7 symlinks removed
Linking /usr/local/Cellar/node@6/6.11.3... 7 symlinks created

If you need to have this software first in your PATH instead consider running:
  echo 'export PATH="/usr/local/opt/node@6/bin:$PATH"' >> ~/.bash_profile
jay@JayMBP:~$ readlink $(which node)
../Cellar/node@6/6.11.3/bin/node
jay@JayMBP:~$ node --version
v6.11.3

@belozer
Copy link

belozer commented Jul 10, 2018

Maybe need use zsh-async?
Works very fast

# Install zsh-async if it’s not present
if [[ ! -a ~/.zsh-async ]]; then
  git clone git@github.com:mafredri/zsh-async.git ~/.zsh-async
fi
source ~/.zsh-async/async.zsh

export NVM_DIR="$HOME/.nvm"
function load_nvm() {
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
    [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
}

# Initialize worker
async_start_worker nvm_worker -n
async_register_callback nvm_worker load_nvm
async_job nvm_worker sleep 0.1

@ljharb
Copy link
Member

ljharb commented Jul 10, 2018

That wouldn't work for all the other non-zsh shells that nvm supports.

@belozer
Copy link

belozer commented Jul 10, 2018

Yes, it is not a box solution. But for local fix it is convenient, because global modules work.

@julioprotzek
Copy link

julioprotzek commented Sep 4, 2018

@belozer solution is by far the fastest one on my machine

@vviikk
Copy link

vviikk commented Sep 13, 2018

@belozer - that is what I needed, after a lot of searching. I was using a zsh nvm plugin (tried a couple of different ones), but your solution is the fastest on OSX & Linux.

narsaynorath added a commit to narsaynorath/dotfiles that referenced this issue Mar 23, 2019
* nvm sourcing with brew installed version is slow and bottlenecks shell
  creation performance
* see nvm-sh/nvm#539
pre1ude added a commit to pre1ude/oh-my-zsh that referenced this issue Jul 2, 2019
use `mafredri/zsh-async` to accelerate the nvm plugin, now works faster

nvm-sh/nvm#539 (comment)
@fudini

This comment has been minimized.

@ljharb

This comment has been minimized.

MarcelRobitaille added a commit to MarcelRobitaille/dotfiles that referenced this issue Oct 10, 2022
Nvm was the slowest part of my prompt. Opening a new shell is super
quick now.

Reference: nvm-sh/nvm#539 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance This relates to anything regarding the speed of using nvm.
Projects
None yet
Development

No branches or pull requests