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 · 49 comments

Comments

Projects
None yet
@dy-dx

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

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 Oct 3, 2014

@zenwalker

This comment has been minimized.

zenwalker commented Jun 10, 2015

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

This comment has been minimized.

Collaborator

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 :-/

@daneroo

This comment has been minimized.

daneroo commented Nov 25, 2015

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

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

This comment has been minimized.

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

bgerm commented Dec 3, 2015

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

@dreyks

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented Jul 25, 2016

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

@dreyks

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented Jul 25, 2016

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

@dreyks

This comment has been minimized.

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

dreyks commented Aug 2, 2016

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

@parasyte

This comment has been minimized.

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

parasyte commented Sep 9, 2016

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

@ljharb

This comment has been minimized.

Collaborator

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

This comment has been minimized.

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.

josephfrazier added a commit to josephfrazier/dotfiles that referenced this issue Feb 6, 2017

@gitaaron

This comment has been minimized.

gitaaron commented Feb 23, 2017

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

This comment has been minimized.

gitaaron commented Feb 23, 2017

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

This comment has been minimized.

Collaborator

ljharb commented Feb 23, 2017

nvm alias default node?

@jmls

This comment has been minimized.

jmls commented Mar 29, 2017

you guys think you have it bad ...

time /usr/local/nvm/nvm.sh                                                                                                                                      
                                                                                                                                                                                          
real    0m15.955s                                                                                                                                                                         
user    0m15.412s                                                                                                                                                                         
sys     0m8.728s               

could someone give me any idea on what could be causing such a problem ?

@ljharb

This comment has been minimized.

Collaborator

ljharb commented Mar 29, 2017

@jmls You have $NVM_DIR set to /usr/local/nvm instead of to the default of ~/.nvm - why is that?

@jmls

This comment has been minimized.

jmls commented Mar 29, 2017

I was following

You can customize the install source, directory, profile, and version using the NVM_SOURCE, NVM_DIR, PROFILE, and NODE_VERSION variables. Eg: curl ... | NVM_DIR=/usr/local/nvm bash for a global install.
@ljharb

This comment has been minimized.

Collaborator

ljharb commented Mar 29, 2017

Whoops, I should really update that; nvm is meant to be per-user, per-shell, and a "global install" just isn't what it's built for.

@ljharb

This comment has been minimized.

Collaborator

ljharb commented Mar 30, 2017

@jmls wait, are you timeing the execution of that file? Or . $NVM_DIR/nvm.sh - sourcing it? nvm.sh is not an executable file nor is meant to be.

@ns-cweber

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented Apr 27, 2017

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

@ns-cweber

This comment has been minimized.

ns-cweber commented Apr 28, 2017

@ljharb That's correct.

@ljharb

This comment has been minimized.

Collaborator

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

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented May 1, 2017

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

@ns-cweber

This comment has been minimized.

ns-cweber commented May 1, 2017

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

ns-cweber commented May 2, 2017

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

This comment has been minimized.

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

This comment has been minimized.

Collaborator

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented Oct 5, 2017

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

@parasyte

This comment has been minimized.

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

michaelmoussa added a commit to michaelmoussa/dotfiles that referenced this issue Apr 23, 2018

Fix nvm slowing down loading new terminal sessions.
See: creationix/nvm#539 (comment)

Also removed the `bash_completion` for nvm, since it was also slowing down new terminal sessions but seems to still get loaded without this line anyway. /shrug
@belozer

This comment has been minimized.

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

This comment has been minimized.

Collaborator

ljharb commented Jul 10, 2018

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

@belozer

This comment has been minimized.

belozer commented Jul 10, 2018

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

@julioprotzek

This comment has been minimized.

julioprotzek commented Sep 4, 2018

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

@vviikk

This comment has been minimized.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment