jenv: jenv init is really slow

I finally got around to trying to work out why my shell startup time was so slow. Turns out that over 70% of the time spent in my .bashrc file is spent executing the eval “$(jenv init -)” line.

$ time eval "$(jenv init -)"

real    0m3.164s
user    0m2.879s
sys 0m0.258s

This seems a bit excessive. I should add that I have a ton of java version installed.

$ jenv versions
  system
  1.6
  1.6.0.65
  1.7
  1.7.0.11
  1.7.0.75
  1.7.0.76
* 1.8 (set by /Users/ggp/.jenv/version)
  1.8.0.20
  1.8.0.25
  1.8.0.31
  1.8.0.40
  1.8.0.45
  1.8.0.51
  1.8.0.60
  1.8.0.65
  1.8.0.66
  1.8.0.71
  1.8.0.72
  1.8.0.73
  1.8.0.74
  1.8.0.77
  1.8.0.91
  1.8.0.92
  oracle64-1.6.0.65
  oracle64-1.7.0.11
  oracle64-1.7.0.67
  oracle64-1.7.0.75
  oracle64-1.7.0.76
  oracle64-1.8.0.20
  oracle64-1.8.0.25
  oracle64-1.8.0.31
  oracle64-1.8.0.40
  oracle64-1.8.0.45
  oracle64-1.8.0.51
  oracle64-1.8.0.60
  oracle64-1.8.0.65
  oracle64-1.8.0.66
  oracle64-1.8.0.71
  oracle64-1.8.0.72
  oracle64-1.8.0.73
  oracle64-1.8.0.74
  oracle64-1.8.0.77
  oracle64-1.8.0.91
  oracle64-1.8.0.92

Bash version is:

$ bash --version
GNU bash, version 4.3.42(1)-release (x86_64-apple-darwin14.5.0)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

About this issue

  • Original URL
  • State: open
  • Created 8 years ago
  • Reactions: 11
  • Comments: 15

Commits related to this issue

Most upvoted comments

I had the same slow shell startup experience on my Mac after the list of *env managers started to grow (currently rbenv, pyenv and jenv). It seems that rehashing is to blame for that (besides ASL lookups).

My research revealed these approaches to speed up shell initialization:

  1. Add all $PATH modifying stuff in .profile, or your shell specific profile (e.g. .bash_profile), as these files are supposed to be sourced only once at the time of login. On Mac, however, all shells are login shells and therefore all login scripts are called each time you start up a shell.

  2. Lazy load your *env manager:

    rbenv() {
      eval "$(command rbenv init -)"
      rbenv "$@"
    }
    

    As the actual init function is called only after the first call to *env. The downside of this approach is that you have to use the default system version at startup.

  3. Initialize your *env without rehashing and (optionally) start a background rehashing process:

    eval "$(jenv init - --no-rehash)"
    (jenv rehash &) 2> /dev/null
    
  4. Use a tool which does not use shims (for ruby there is chruby) in combination with direnv.

For me, option no. 3 seems like the best approach. I see no drawbacks, it imposes only minor modifications, is applicable to all rbenv clones and it has sped up my shells startup time by factor ~1,75:

Before:

$ for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done
        1.17 real         0.71 user         0.40 sys
        1.17 real         0.71 user         0.39 sys
        1.13 real         0.70 user         0.39 sys
        1.16 real         0.71 user         0.40 sys
        1.11 real         0.71 user         0.39 sys
        1.12 real         0.72 user         0.39 sys
        1.10 real         0.70 user         0.38 sys
        1.11 real         0.70 user         0.39 sys
        1.10 real         0.70 user         0.39 sys
        1.10 real         0.70 user         0.39 sys

After:

$ for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done
        0.63 real         0.26 user         0.25 sys
        0.64 real         0.26 user         0.26 sys
        0.64 real         0.26 user         0.25 sys
        0.65 real         0.26 user         0.25 sys
        0.66 real         0.27 user         0.26 sys
        0.61 real         0.26 user         0.25 sys
        0.65 real         0.26 user         0.26 sys
        0.65 real         0.26 user         0.25 sys
        0.63 real         0.26 user         0.25 sys
        0.65 real         0.26 user         0.25 sys

Hope that helps.

Sorry for reply to dead topic, but in case someone else ends here and wants to have even faster shell start - put the eval into .profile or .zprofile for zshrc, so it is loaded only once after login.

before:

for i in $(seq 1 10); do /usr/bin/time zsh-i -c exit; done
        0.47 real         0.21 user         0.18 sys
        0.44 real         0.20 user         0.17 sys
        0.43 real         0.20 user         0.16 sys
        0.68 real         0.22 user         0.18 sys
        0.42 real         0.19 user         0.16 sys
        0.42 real         0.19 user         0.16 sys
        0.42 real         0.19 user         0.16 sys
        0.42 real         0.19 user         0.16 sys
        0.42 real         0.19 user         0.16 sys
        0.44 real         0.20 user         0.16 sys

after

for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done
        0.19 real         0.09 user         0.08 sys
        0.17 real         0.08 user         0.08 sys
        0.17 real         0.08 user         0.07 sys
        0.16 real         0.07 user         0.07 sys
        0.15 real         0.07 user         0.06 sys
        0.16 real         0.07 user         0.07 sys
        0.17 real         0.07 user         0.07 sys
        0.15 real         0.07 user         0.06 sys
        0.15 real         0.07 user         0.07 sys
        0.15 real         0.07 user         0.06 sys

Thanks @zkyangc, for anyone applying his solution, make sure to also define for other tools that depend on Java, e.g. Maven:

load_jenv() {
  unset -f java jenv mvn
  eval "$(jenv init -)"
}
jenv() {
  load_jenv
  jenv $@
}
java() {
  load_jenv
  java $@
}
mvn() {
  load_jenv
  mvn $@
}

For zsh init:

lazy_load_jenv() {
  unset -f java jenv
  eval export PATH="${HOME}/.jenv/shims:${PATH}"
  eval "$(jenv init -)"
}

jenv() {
  lazy_load_jenv
  jenv $@
}

java() {
  lazy_load_jenv
  java $@
}

Sorry for reply to dead topic, but in case someone else ends here and wants to have even faster shell start - put the eval into .profile or .zprofile for zshrc, so it is loaded only once after login.

Mind that for macos, every new window/tab is a login shell, so essentially there’s no difference… your benchmark script would have to be /usr/bin/time zsh -il -c exit for macos

https://unix.stackexchange.com/questions/119627/why-are-interactive-shells-on-osx-login-shells-by-default