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

Performance: nvm use takes about a second #860

Closed
dylanpyle opened this issue Oct 6, 2015 · 76 comments · Fixed by #2317
Closed

Performance: nvm use takes about a second #860

dylanpyle opened this issue Oct 6, 2015 · 76 comments · Fixed by #2317
Labels
performance This relates to anything regarding the speed of using nvm. pull request wanted This is a great way to contribute! Help us out :-D

Comments

@dylanpyle
Copy link

dylanpyle commented Oct 6, 2015

(Pulling some details out of the thread on #703 per request from @ljharb)

nvm use is slow. This makes shell startup slow, and switching versions after shell startup slow too.

$ time (nvm use 0.10)
Now using node v0.10.38 (npm v1.4.28)
( nvm use 0.10; )  0.50s user 0.20s system 83% cpu 0.846 total

$ time (nvm use 4)
Now using node v4.1.2 (npm v2.14.4)
( nvm use 4; )  0.72s user 0.23s system 86% cpu 1.089 total

System information:

  • nvm 0.28.0
  • zsh 5.0.8 (but same issue appears in bash/sh)
  • osx 10.10.5
$ nvm debug
$SHELL: /bin/zsh
$NVM_DIR: '$HOME/.nvm'
$PREFIX: ''
$NPM_CONFIG_PREFIX: ''
nvm current: v0.10.38
which node: $NVM_DIR/v0.10.38/bin/node
which iojs: iojs not found
which npm: $NVM_DIR/v0.10.38/bin/npm
npm config get prefix: $NVM_DIR/v0.10.38
npm root -g: $NVM_DIR/v0.10.38/lib/node_modules

My temporary solution is as follows:

$ nvm unalias default

— then adding this hack in my zshrc

@behrangsa
Copy link

It's very slow for me too. My SSD is a few years old so it is not the latest and greatest, but I guess nvm should not make execution of my bash ~/.profile script this slow. It adds an extra 1.2 seconds delay to my Teminal whenever I open a new tab:

export NVM_DIR=~/.nvm
time source $(brew --prefix nvm)/nvm.sh

This outputs:

real    0m1.175s
user    0m0.863s
sys 0m0.256s
$ nvm debug
$SHELL: /bin/bash
$NVM_DIR: $HOME/.nvm
nvm current: v4.1.1
which node: $NVM_DIR/versions/node/v4.1.1/bin/node
which iojs: 
which npm: $NVM_DIR/versions/node/v4.1.1/bin/npm
npm config get prefix: $NVM_DIR/versions/node/v4.1.1
npm root -g: $NVM_DIR/versions/node/v4.1.1/lib/node_modules

@ljharb
Copy link
Member

ljharb commented Oct 10, 2015

@behrangsa nvm with homebrew is not supported. can you try installing properly, with the curl command in the readme, and see if it's any different? (note that brew --prefix nvm may be taking some time too)

@behrangsa
Copy link

@ljharb Hi Jordan,

I hardcoded brew --prefix nvm to /usr/local/opt/nvm and it made loading nvm a bit faster, but it is still slower than one would expect IMHO:

First load:

real    0m1.380s
user    0m0.883s
sys 0m0.276s

Subsequent loads (in new tabs):

real    0m0.696s
user    0m0.582s
sys 0m0.144s

So I guess this is more or less inline with @dylanpyle's findings, even though that I've installed nvm via brew.

@ljharb
Copy link
Member

ljharb commented Oct 10, 2015

Thanks, I appreciate that - I doubt installing with brew affects the speed of nvm use, so your timing info is helpful. What's nvm --version say btw? (I just added this in nvm debug in c957989)

@behrangsa
Copy link

It is 0.27.1.

@ljharb
Copy link
Member

ljharb commented Oct 10, 2015

@behrangsa can you try with 0.29.0 and see if it's any better?

@behrangsa
Copy link

I just upgraded to 0.29.0 and the performance is almost the same:

$ nvm --version
0.29.0
real  0m0.653s
user  0m0.542s
sys   0m0.137s

@ljharb
Copy link
Member

ljharb commented Oct 10, 2015

thanks - i'll keep looking into it

@behrangsa
Copy link

Thanks @ljharb! 👍

@dylanpyle
Copy link
Author

More data: I'm seeing the same issue on a different machine with a similar setup:

  • nvm 0.29.0
  • osx 10.11
  • zsh 5.0.6

Issue is present even with everything non-nvm related removed from zshrc - hopefully ruling out conflicting scripts.

ryanwilsonperkin added a commit to ryanwilsonperkin/dotfiles that referenced this issue Oct 13, 2015
Until nvm-sh/nvm#860 is solved and nvm.sh
is optimized for speed, this will only export the script path, invoking
it must be done explicitly.

I use this workflow by opening a new shell, and running
`source $NVM_SCRIPT`.
@sebicas
Copy link

sebicas commented Oct 23, 2015

Same problem here:

  • nvm 0.28.0
  • osx 10.10.5

@ljharb
Copy link
Member

ljharb commented Oct 23, 2015

@sebicas what's the output of nvm debug? What shell are you using?

@sebicas
Copy link

sebicas commented Oct 23, 2015

@ljharb here is the output

$ nvm debug
$SHELL: /bin/bash
$NVM_DIR: '$HOME/.nvm'
$PREFIX: ''
$NPM_CONFIG_PREFIX: ''
nvm current: v4.2.1
which node: $NVM_DIR/versions/node/v4.2.1/bin/node
which iojs: 
which npm: $NVM_DIR/versions/node/v4.2.1/bin/npm
npm config get prefix: $NVM_DIR/versions/node/v4.2.1
npm root -g: $NVM_DIR/versions/node/v4.2.1/lib/node_modules

I am using the default osx shell

@ljharb
Copy link
Member

ljharb commented Oct 23, 2015

Thanks - by chance do you have an SSD, or a standard HDD?

@sebicas
Copy link

sebicas commented Oct 23, 2015

Standard HDD is a Western Digital Scorpio Blue (750GB WD7500BPVT) just in case.

@jcpst
Copy link

jcpst commented Oct 29, 2015

Hi, noticing the same problem here. I isolated the lag in my shell loading down to the sourcing of the nvm script.

  • nvm 0.29.0
  • Arch Linux
  • Zsh
  • SSD

@bittner
Copy link

bittner commented Nov 1, 2015

I have "solved" this issue for me the following, pragmatic way in ~/.bashrc: (Ubuntu 14.04.3 LTS)

alias node='unalias node npm && nvm use v4.2.1 && node'
alias npm='node -v > /dev/null && npm'  # optional

This way my shell always starts up fast, and until I need node for the first time there is no delay.

With the second line also npm is supported in the same fashion. (Here, it's actually important that the unalias happens before nvm use is called. I guess, due to a reference to npm in the setup of node.)

@ljharb
Copy link
Member

ljharb commented Nov 1, 2015

Note that that workaround will mean all of your globally-installed modules will be unavailable until after you've referenced node.

@decached
Copy link

decached commented Nov 6, 2015

+1

I have the same problem. I use prezto and I don't think the shell or the shell-manager is causing problem.

  • nvm 0.29.0
  • zsh 5.1.1
  • Ubuntu 15.10

Prezto zsh without nvm

$ time /bin/zsh -i -c exit
/bin/zsh -i -c exit  0.14s user 0.07s system 92% cpu 0.230 total

Prezto zsh with nvm

$ time /bin/zsh -i -c exit
/bin/zsh -i -c exit  0.57s user 0.10s system 86% cpu 0.781 total

Vanilla zsh without nvm

$ time /bin/zsh -i -c exit
/bin/zsh -i -c exit  0.01s user 0.00s system 74% cpu 0.016 total

Vanilla zsh with nvm

$ time /bin/zsh -i -c exit
/bin/zsh -i -c exit  0.52s user 0.08s system 88% cpu 0.676 total

nvm.sh seems to be taking 0.5s on average to load up.

@ibash
Copy link

ibash commented Nov 19, 2015

Anecdote:

I tracked down my slow shell to nvm as well. I found the following things slow:

In my .bash_profile I had:

export NVM_DIR=~/.nvm
source `brew --prefix nvm`/nvm.sh

Findings:

  • brew --prefix nvm was very slow
  • When I commented out export NVM_DIR=~/.nvm I found a huge speed improvement

My two solutions were:

  • Change brew --prefix nvm to the absolute path
  • I blew away ~/.nvm and reinstalled the versions of node I needed. I suspect that it had something to do with versions of node installed with an older version of nvm.

@ljharb
Copy link
Member

ljharb commented Nov 19, 2015

@ibash nvm is utterly unsupported on homebrew. please don't install it that way, and use the curl command in the readme. nvm doesn't install node versions any differently than it used to, so I don't think that's the problem.

@ibash
Copy link

ibash commented Nov 19, 2015

Just reinstalled w/o homebrew. You're right, I think what I was seeing was a side effect of blowing away my alias default.

@kilianc
Copy link
Contributor

kilianc commented Dec 7, 2015

Same over here

$ source ~/.nvm/nvm.sh

takes ~0.7s to execute

edit

sourcing is fine if I nvm unalias default the bottleneck is nvm use

@kilianc
Copy link
Contributor

kilianc commented Dec 7, 2015

I commented out part of the script for nvm use and my shell now loads instantly, see https://github.com/creationix/nvm/blob/master/nvm.sh#L1806

diff --git a/nvm.sh b/nvm.sh
index c6b8f83..0ad90f1 100755
--- a/nvm.sh
+++ b/nvm.sh
@@ -1805,11 +1805,11 @@ nvm() {

       # This nvm_ensure_version_installed call can be a performance bottleneck
       # on shell startup. Perhaps we can optimize it away or make it faster.
-      nvm_ensure_version_installed "$PROVIDED_VERSION"
-      EXIT_CODE=$?
-      if [ "$EXIT_CODE" != "0" ]; then
-        return $EXIT_CODE
-      fi
+      # nvm_ensure_version_installed "$PROVIDED_VERSION"
+      # EXIT_CODE=$?
+      # if [ "$EXIT_CODE" != "0" ]; then
+      #   return $EXIT_CODE
+      # fi

       local NVM_VERSION_DIR
       NVM_VERSION_DIR="$(nvm_version_path "$VERSION")"
@@ -1836,28 +1836,28 @@ nvm() {
         command rm -f "$NVM_DIR/current" && ln -s "$NVM_VERSION_DIR" "$NVM_DIR/current"
       fi
       local NVM_USE_OUTPUT
-      if nvm_is_iojs_version "$VERSION"; then
-        if [ $NVM_USE_SILENT -ne 1 ]; then
-          NVM_USE_OUTPUT="Now using io.js $(nvm_strip_iojs_prefix "$VERSION")$(nvm_print_npm_version)"
-        fi
-      else
-        if [ $NVM_USE_SILENT -ne 1 ]; then
-          NVM_USE_OUTPUT="Now using node $VERSION$(nvm_print_npm_version)"
-        fi
-      fi
-      if [ "_$VERSION" != "_system" ]; then
-        local NVM_USE_CMD
-        NVM_USE_CMD="nvm use --delete-prefix"
-        if [ -n "$PROVIDED_VERSION" ]; then
-          NVM_USE_CMD="$NVM_USE_CMD $VERSION"
-        fi
-        if [ $NVM_USE_SILENT -eq 1 ]; then
-          NVM_USE_CMD="$NVM_USE_CMD --silent"
-        fi
-        if ! nvm_die_on_prefix "$NVM_DELETE_PREFIX" "$NVM_USE_CMD"; then
-          return 11
-        fi
-      fi
+      # if nvm_is_iojs_version "$VERSION"; then
+      #   if [ $NVM_USE_SILENT -ne 1 ]; then
+      #     NVM_USE_OUTPUT="Now using io.js $(nvm_strip_iojs_prefix "$VERSION")$(nvm_print_npm_version)"
+      #   fi
+      # else
+      #   if [ $NVM_USE_SILENT -ne 1 ]; then
+      #     NVM_USE_OUTPUT="Now using node $VERSION$(nvm_print_npm_version)"
+      #   fi
+      # fi
+      # if [ "_$VERSION" != "_system" ]; then
+      #   local NVM_USE_CMD
+      #   NVM_USE_CMD="nvm use --delete-prefix"
+      #   if [ -n "$PROVIDED_VERSION" ]; then
+      #     NVM_USE_CMD="$NVM_USE_CMD $VERSION"
+      #   fi
+      #   if [ $NVM_USE_SILENT -eq 1 ]; then
+      #     NVM_USE_CMD="$NVM_USE_CMD --silent"
+      #   fi
+      #   if ! nvm_die_on_prefix "$NVM_DELETE_PREFIX" "$NVM_USE_CMD"; then
+      #     return 11
+      #   fi
+      # fi
       if [ -n "$NVM_USE_OUTPUT" ]; then
         echo "$NVM_USE_OUTPUT"
       fi

@ljharb
Copy link
Member

ljharb commented Dec 7, 2015

@kilianc Thanks - it's likely mostly the nvm_die_on_prefix command. if you comment out just those three lines, how fast does it load?

@kilianc
Copy link
Contributor

kilianc commented Dec 7, 2015

@ljharb I tried various combinations but everytime I uncomment something it adds between 100 to 300ms.

This is pretty slow $(nvm_print_npm_version) also

@ljharb
Copy link
Member

ljharb commented Dec 7, 2015

Yes, invoking npm seems to add a bunch of time. I could easily remove printing the npm version, but nvm_die_on_prefix is critically important to run.

@kilianc
Copy link
Contributor

kilianc commented Dec 7, 2015

@ljharb I know, but from my perspective I am not going to uncomment those line unless it turns out I broke something :D it's 1s vs 30ms

@alangpierce
Copy link

I was able to speed up shell init significantly by running nvm.sh with --no-use and then just setting the PATH directly to what nvm use would have done, like this:

NODE_VERSION="v4.4.7"
. "$NVM_DIR/nvm.sh" --no-use
export PATH="${PATH}:${NVM_DIR}/versions/node/${NODE_VERSION}/bin"

As far as I can tell, for my simple case, this seems to work just as well as nvm use (although it would be good to hear if there's other state that's not being set correctly). Since it hard-codes my default node version, it obviously is not something that could be put in nvm, but for me it's not that much trouble to maintain that line.

MoOx added a commit to MoOx/setup that referenced this issue Aug 29, 2016
ulfmagnetics added a commit to ulfmagnetics/dotfiles that referenced this issue Dec 7, 2016
@thom4parisot
Copy link

I totally understand the need to avoid hacky solutions and to cut corners – I guess there are plenty; shells are so versatiles.

Would it be reasonable to know what could help improve solve the problem though? What is the path to resolution?

'cause if using nvm decreases the developer experience in the terminal, it defeats the purpose of the tool itself – and nvm is great so it does not deserve to be undermined by this loading performance issue.

@ljharb
Copy link
Member

ljharb commented Dec 11, 2016

@oncletom very diplomatically said. the main slowdown is npm config get prefix - if someone can come up with a reliable way to replicate that without the slowdown, I'd be happy to use that instead. See npm/npm#14458 for some background.

AGhost-7 added a commit to AGhost-7/nvm that referenced this issue Apr 18, 2017
People who actually read the docs should not suffer - see nvm-sh#860 and nvm-sh#703
@0xcaff
Copy link

0xcaff commented Jun 17, 2017

Here's a script to lazy load nvm when using bash as your shell:

# Set up NVM
# Lazily initialize nvm to keep shell start up time fast.
export NVM_DIR="$HOME/.nvm"
export NVM_SH="$NVM_DIR/nvm.sh"
# https://github.com/creationix/nvm/issues/860
declare -a NODE_GLOBALS=(`find $NVM_DIR/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)

NODE_GLOBALS+=("node")
NODE_GLOBALS+=("nvm")

load_nvm () {
    # echo "Loading NVM..."
    [ -s "$NVM_SH" ] && source "$NVM_SH"
    # echo "Loaded NVM"
}

unhook_nvm_load () {
    # echo "Unhooking NVM Lazy Loader"
    for cmd in "${NODE_GLOBALS[@]}"; do
        unset -f "${cmd}"
    done
    # echo "Unhooked NVM Lazy Loader"
}

for cmd in "${NODE_GLOBALS[@]}"; do
    eval "function ${cmd} () { unhook_nvm_load; load_nvm; ${cmd} \$@; }"
done

@ljharb
Copy link
Member

ljharb commented Jun 17, 2017

@0xcaff see #860 (comment) (specifically, what if a global is available in one version of node but not another, and i try to use it)

@0xcaff
Copy link

0xcaff commented Jun 17, 2017

Good, point. It will fail in that case. I only use nvm with one version of node, the latest, so it always works for me. I agree that is is not optimal.

@defunctzombie
Copy link

@ljharb earlier comments suggest that there are corner cases to consider for iojs and older nodejs versions. If a user is no longer interested in those, is there a way to make this script faster?

@ljharb
Copy link
Member

ljharb commented Jun 21, 2017

@defunctzombie the speed here is 100% unrelated to node or io.js version; as it's solely caused by every version of npm running npm config get prefix slowly. Can you point me to a specific comment that claims it's related?

@TylerBrock
Copy link

TylerBrock commented Jun 24, 2017

Why does npm config get prefix run so slowly? Presumably it's doing a trivial amount of work.

@ljharb
Copy link
Member

ljharb commented Jun 25, 2017

@TylerBrock it's not as trivial as you'd think; it merges any .npmrc file crawling up to /, and then it overlays, case-insensitive, any NPM_CONFIG_BLAH env vars (ie, NpM_cOnFig_blah works too) - and then overlays some other env vars, and then comes up with a config. That said, it should still be quick. What would be ideal is if npm created a separate package, that they consumed in the npm CLI, that covered all npm config commands - then we could use that :-)

@TylerBrock
Copy link

TylerBrock commented Jun 25, 2017

Ok, fair enough. I might not know enough about the inner workings and capabilities here but why does nvm need to know what npm thinks the prefix is? Can we skip this interrogation altogether?

Perhaps we could set some sort of NVM_PATH that gets us the root where we store all the node installations. Then we can get the version of node we want (or default) from the closest nvmrc to the CWD thereby breaking all this down to a simple path concatenation.

Example:

nvm config get prefix on my computer says: /Users/tbrock/.nvm/versions/node/v6.11.0
If my NVM_PATH was $HOME/.nvm then I'd just read the .nvmrc closest to me and do:

export prefix=${NVM_PATH}/versions/node/${NVM_NODE_VERSION}

Searching upwards to the find the closest nvmrc should take some time but it shouldn't take 500ms+. Every other verison manager has figured out how to make this operation fast we should be able to do it as well.

@ljharb
Copy link
Member

ljharb commented Jun 25, 2017

@TylerBrock because if npm's prefix is overridden (from the nvm-managed path), then nvm can't work properly - and people often have it set from trying incorrect tutorials prior to trying nvm.

I agree that it shouldn't be slow; I believe it's because npm config always bootstraps the entire npm CLI even if it's only running the config stuff.

(Also note it's npm config, not nvm config, and npmrc, not nvmrc)

I certainly could just always override the prefix setting; but since it can be set any number of ways, it's much safer to require the user to manually remove the override.

AGhost-7 added a commit to AGhost-7/nvm that referenced this issue Aug 18, 2017
People who actually read the docs should not suffer - see nvm-sh#860 and nvm-sh#703
@caleb531
Copy link

@ljharb Maybe this is a terrible idea, but hear me out:

For the people who read the docs, perhaps some sort of --bypass-prefix-check option could be added, which the user would supply when sourcing nvm.sh in their shell config. The use of the option would be the user's affirmation that they are not using a prefix and that they understand the risks of having one set.

You see, I'm currently using n to manage my multiple node versions, but I'm not a fan of the way it manages node (i.e. use of sudo and large binaries in /usr/local/bin). I've been considering nvm for a while now, but the shell startup slowdown has always been an absolute deal-breaker for me.

At this point, I'm sorely tempted to throw in the towel and just use @AGhost-7's recent patch (AGhost-7@892dbb9), which removes the prefix check entirely. At the same time, however, I'd prefer to use the official nvm so I can benefit from future improvements to the software.

@ljharb
Copy link
Member

ljharb commented Aug 26, 2017

@caleb531 i'd be all in favor of that, except that the instant there's an official way to bypass the checking, it's going to show up in tweets, blog posts, and SO posts where people just blindly copy-paste it to "get things working". That's not going to ensure that the majority "understand the risks".

@caleb531
Copy link

@ljharb Fair enough. Here's another idea: have nvm only check the prefix until it encounters the default prefix (either because the user removed their custom prefix, or if the prefix was never changed in the first place).

The logic could work like the following, probably using some file under NVM_DIR to represent when nvm detects the default prefix (I'll call this the "flag file"). If this flag file doesn't exist, always check the prefix as needed. If it does exist and/or it contains the right data, skip the prefix check.

  1. On installation, check the prefix. Abort the installation if a custom prefix is set.
  2. When the user invokes nvm for the first time (post-installation), check the prefix. If the prefix is non-default, warn the user and abort. If the prefix is the default, update the flag file
  3. Whenever nvm is updated or reinstalled, reset the flag file so that the prefix will be checked again.

Theoretically, you could check the prefix periodically on a certain interval (i.e. if the last check was more than a week ago), but that feels rather arbitrary. I'd rather go with the above approach: checking on installation, update/reinstall, and first invocation of nvm—aborting in all cases if the prefix is incorrect.

I personally think this is a reasonable approach, though it really depends on how likely the user would change the prefix after they've installed nvm. I suspect that's not very likely, but I'd be curious to hear what anyone else thinks.

@ljharb
Copy link
Member

ljharb commented Dec 20, 2017

#1679 exhaustively covers all the environment variables that npm checks for, and I'm relatively sure it's pretty fast.

I believe the remaining piece to remove npm usage entirely will be to check .npmrc files upwards to /, and error out if any of the lines has a "prefix" setting.

@ouchxp
Copy link

ouchxp commented Jan 18, 2018

Here's my performance hacks, it help nvm use speed up to 54ms
https://github.com/ouchxp/nvm/commit/dd026062efee5055c79ec01ed9fdcba987ee182f

@nath1as

This comment has been minimized.

@MoOx
Copy link

MoOx commented Oct 3, 2019

Consider using a fnm. Performances are stellar.

@ouchxp

This comment has been minimized.

@Rush
Copy link

Rush commented Nov 6, 2020

Is the issue closed because it's fixed? @ljharb

@ljharb
Copy link
Member

ljharb commented Nov 6, 2020

@Rush yes! See the linked PR.

v0.37.0 is released with the fix. If you’re still seeing issues after updating and restarting your terminal, please file a new issue and fill out the issue template.

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. pull request wanted This is a great way to contribute! Help us out :-D
Projects
None yet
Development

Successfully merging a pull request may close this issue.