pyenv-virtualenv icon indicating copy to clipboard operation
pyenv-virtualenv copied to clipboard

Slow shell performance after running pyenv virtualenv-init

Open cmcginty opened this issue 6 years ago • 54 comments

It looks like there is some issue on my system that is causing very poor runtime performance after running pyenv virtualenv-init. Related to #132

I think the issue is with pyenv verision-name. What steps can I take to debug this further?

cmcginty avatar Apr 20 '18 18:04 cmcginty

This issue is caused by a 100ms delay added to every script executed with $()command substitution in the code. The entry point of pyenv sh-activate has number of sub-commands that get executed with this call. The call trace I'm seeing is:

pyenv sh-activate
-> pyenv-sh-activate                +100ms
  -> pyenv-version-name             +100ms
    -> pyenv-version-file           +100ms
    -> pyenv-version-file-read      +100ms
    -> pyenv-hooks                  +100ms
-> pyenv-virtualenv-prefix          +100ms
  -> pyenv-version-name             +100ms
    -> pyenv-version-file           +100ms
    -> pyenv-version-file-read      +100ms
    -> pyenv-hooks                  +100ms

All these calls add up to 1s+ of latency.

My theory is this is related to a virus scanner or carbonblack.

cmcginty avatar Apr 30 '18 02:04 cmcginty

More progress. I can reproduce outside of pyenv now. 😄 I wrote a shell script that creates and calls another script. If the script has a shebang line in it, then the execution is horrendous.

#!/bin/bash
SUB_SCRIPT=$(mktemp)
if [[ $1 == "--bash" ]]; then
  echo "#!/bin/bash" > $SUB_SCRIPT
fi
echo "exit" >> $SUB_SCRIPT
chmod +x $SUB_SCRIPT
for X in $(seq 100); do
  $($SUB_SCRIPT)
done
rm $SUB_SCRIPT
$ time ./test.sh
./test.sh  0.05s user 0.08s system 37% cpu 0.341 total

$ time ./test.sh --bash
./test.sh --bash  0.24s user 0.23s system 3% cpu 11.869 total

Even on my unaffected system, the performance is measurably worse when shebang line exists.

$ time ./test.sh
./test.sh  0.07s user 0.08s system 94% cpu 0.156 total

$ time ./test.sh --bash
./test.sh --bash  0.15s user 0.16s system 87% cpu 0.351 total

cmcginty avatar Apr 30 '18 03:04 cmcginty

running into the same problem too, on Ubuntu 16.04.

Honghe avatar May 18 '18 03:05 Honghe

@Honghe Does your laptop run CarbonBlack? That was the root cause of my performance issue.

cmcginty avatar May 18 '18 05:05 cmcginty

@cmcginty no running CarbonBlack ;(

Honghe avatar May 18 '18 09:05 Honghe

Came here looking for a solution too. Added this to my zsh config and saw terrible performance. On Ubuntu 18.04. Removing this eval "$(pyenv virtualenv-init -)" removes the slowness.

chriscarpenter12 avatar Jan 02 '19 02:01 chriscarpenter12

Experiencing this^ too.

Baishan avatar Jan 07 '19 15:01 Baishan

Similar problem on Debian 9 with zsh. Removing eval "$(pyenv virtualenv-init -)" from ~/.zshrc fixes it.

jtherrmann avatar Mar 26 '19 06:03 jtherrmann

any news ?

SakiiR avatar Jun 13 '19 08:06 SakiiR

What does eval "$(pyenv virtualenv-init -)" actually do? I just commented it out and have still been able to use pyenv with virtualenv with no problems.

jtherrmann avatar Jun 15 '19 19:06 jtherrmann

What does eval "$(pyenv virtualenv-init -)" actually do? I just commented it out and have still been able to use pyenv with virtualenv with no problems.

It can automatically activate virtualenvs when a .python-version file is present.

shawnohare avatar Sep 11 '19 19:09 shawnohare

What does eval "$(pyenv virtualenv-init -)" actually do? I just commented it out and have still been able to use pyenv with virtualenv with no problems.

It can automatically activate virtualenvs when a .python-version file is present.

So it just removes the need to run source venv/bin/activate?

jtherrmann avatar Sep 14 '19 19:09 jtherrmann

Yes, echo $(pyenv virtualenv-init -) makes every command slow. This command run following set of command while starting the shell.

export PATH="/home/mayank/.pyenv/plugins/pyenv-virtualenv/shims:${PATH}";
export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() {
  local ret=$?
  if [ -n "$VIRTUAL_ENV" ]; then
    eval "$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
  else
    eval "$(pyenv sh-activate --quiet || true)" || true
  fi
  return $ret
};
typeset -g -a precmd_functions
if [[ -z $precmd_functions[(r)_pyenv_virtualenv_hook] ]]; then
  precmd_functions=(_pyenv_virtualenv_hook $precmd_functions);
fi

Hence, other than adding virtualenv in $PATH, it appends a hook to be run before every command. The only purpose of the hook is to activate virtualenvs when a .python-version file is present.

mayanksuman avatar Jan 25 '21 13:01 mayanksuman

Solution

Warning: the solution is far from perfect, it breaks command pyenv activate/deactivate. Use it only if the slowness really bothers you.

Quick Solution

  1. Unload hook func _pyenv_virtualenv_hook
  2. Never use pyenv activate/deactivate, cause they're broken without the above hook. Stick with pyenv shell env_name, pyenv shell --unset instead.

For ZSH and Bash

# Init pyenv-virtualenv, but 
# unload precmd hook _pyenv_virtualenv_hook
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

# Warning: unloading the following hook breaks command
# `pyenv activate/deactivate`. Please switch to
# `pyenv shell env_name`, `pyenv shell --unset` instead.
if [[ -n $ZSH_VERSION ]]; then
  autoload -Uz add-zsh-hook
  add-zsh-hook -D precmd _pyenv_virtualenv_hook
fi
if [[ -n $BASH_VERSION ]]; then
  PROMPT_COMMAND="${PROMPT_COMMAND/_pyenv_virtualenv_hook;/}"
fi

laggardkernel avatar Mar 26 '21 08:03 laggardkernel

Slow shell performance with pyenv-virtualenv enabled can be caused by some antivirus software, in my case it was ESET. I compared two macbooks with same MacOS version (Catalina), one with and one without ESET. On the system with antivirus installed shell was very sluggish, on the other system its responsiveness was normal. After upgrade to MacOS Big Sur ESET antivirus was removed (because of same incompatibility) and the responsiveness of comamnd prompt returned back to normal.

jbojar avatar May 14 '21 23:05 jbojar

I'm finding this slow as well. I'm on latest Arch Linux, nothing special about my configuration, and yet:

$ time _pyenv_virtualenv_hook

real	0m0.220s
user	0m0.160s
sys	0m0.070s

This is on the high end. For me it varies quite a bit, anywhere from 80 ms to 225 ms. This is way too much delay to add to every shell invocation.

The solution posted above is useful, but it does mean that activation is no longer automatic. My scripts check that PYENV_VERSION is correct to force me to activate manually which is a useable workaround.

ludocode avatar Jun 08 '21 18:06 ludocode

I am getting this as well on pyenv 1.2.25-2482-g9a42b48d and its previous versions. Having virtualenv-init in my .bashrc results in sensible delay even for just pressing enter with empty string in bash on ubuntu 18.04. I do not have zsh, fish, elvish or anything else installed on top, just plain bash. Without virtualenv-init the next line in prompt appears instantly.

paprikman avatar Oct 07 '21 03:10 paprikman

To find out what takes time, we need to get a debug trace of what's running while you perform those actions and how long it takes.

For the first point

export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
<reproduce the problem>
set +x

For the second point, using $SECONDS in PS4 may help. (I'm not sure what it does, but it seems to expand to the time since last shell prompt or invocation.) It that var doesn't help, you'll need to intersperse the code that runs when you reproduce the problem with time calls and use those to localize the time hog.

native-api avatar Oct 07 '21 08:10 native-api

To find out what takes time, we need to get a debug trace of what's running while you perform those actions and how long it takes.

For the first point

export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
<reproduce the problem>
set +x

For the second point, using $SECONDS in PS4 may help. (I'm not sure what it does, but it seems to expand to the time since last shell prompt or invocation.) It that var doesn't help, you'll need to intersperse the code that runs when you reproduce the problem with time calls and use those to localize the time hog.

I have tried doing the above, but it did not make it more clearer in any way. Again, just pressing enter with empty input in shell results in a small yet very noticeable delay before next prompt. It only occurs (in my config) when I am using the pyenv-virtualenv plugin. It does not matter if some virtualenv is active or not. I do not have sensible knowledge in that area of programming to dig it deeper, unfortunately. By the way, the delay is a lot more pronounced when connected over ssh.

I am gonna use other tools for now then. Thank you.

paprikman avatar Oct 11 '21 08:10 paprikman

just pressing enter with empty input in shell results in a small yet very noticeable delay before next prompt.

With the code I gave, you should've also seen the trace of any automatic commands that run at this moment. Weren't there any output?

What does this produce for you?

trap -p
echo "$PROMPT_COMMAND"

native-api avatar Oct 11 '21 23:10 native-api

just pressing enter with empty input in shell results in a small yet very noticeable delay before next prompt.

With the code I gave, you should've also seen the trace of any automatic commands that run at this moment. Weren't there any output?

What does this produce for you?

trap -p
echo "$PROMPT_COMMAND"

Thank you for the updated instructions. I have used the following script to test the behavior:

#!/usr/bin/env bash

export PS4='+(${BASH_SOURCE}:${LINENO}) $SECONDS: ${FUNCNAME[0]:+${FUNCNAME[0]}(): } '
set -x
ls
set +x

which results in

+(./virtualenv-prompt.sh:5) 0: main():  ls
dark  out  parse.py  schemenames.json  virtualenv-prompt.sh
+(./virtualenv-prompt.sh:6) 0: main():  set +x

The code snippet you've posted outputs an empty string. I have also reinstalled pyenv and the plugin, which has sped up things by a lot. If I just keep hitting enter the new line appears almost instantly. I have (probably?) had bad install or older version. It seems like pyenv wouldn't update to its 2+ version using the built-in updater.

paprikman avatar Oct 12 '21 07:10 paprikman

Same issue here on Raspberry Pi4

weyou avatar Nov 16 '21 05:11 weyou

Using WSL1 Ubuntu 20.04, on the latest version of pyenv and pyenv virtualenv. Without adding

eval "$(pyenv virtualenv-init -)"

in my .zshrc, the very useful tool zsh-prompt-benchmark (from https://github.com/romkatv/zsh-prompt-benchmark), gives: image

Nonetheless, after adding it to my .zshrc, I obtain from the same tool: image

ginolegigot avatar Dec 17 '21 19:12 ginolegigot

On zsh replacing the precmd hook with a precwd hook seems like an ok workaround.

In my zshrc: eval "$(pyenv virtualenv-init - | sed s/precmd/precwd/g)"

then:

~/src $ python --version
Python 2.7.16
~/src $ cd project
~/src/project $ python --version
Python 3.9.9

With precwd hook:

********************************************************************
                      Prompt Benchmark Results
********************************************************************
Warmup duration      8s
Benchmark duration   2.029s
Benchmarked prompts  56
Time per prompt      36.24ms  <-- prompt latency (lower is better)
********************************************************************

With precmd hook:

********************************************************************
                      Prompt Benchmark Results
********************************************************************
Warmup duration      8s
Benchmark duration   2.174s
Benchmarked prompts  6
Time per prompt      362.39ms  <-- prompt latency (lower is better)
********************************************************************

monopoler08 avatar Jan 07 '22 14:01 monopoler08

Thanks @monopoler08 your solution did not work for me, I did not have a precwd but chpwdevent. I changed your command with:

eval "$(pyenv virtualenv-init - | sed s/precmd/chpwd/g)"

And it worked perfectly.

RemiDesgrange avatar Apr 12 '22 06:04 RemiDesgrange

For me, it's the pyenv sh-activate --quiet (called by _pyenv_virtualenv_hook in my PROMPT_COMMAND) that is taking up the bulk of the time.

david-drinn avatar Aug 08 '22 18:08 david-drinn

After noticing very slow startup time for a new terminal session and a noticeable delay to print a new prompt, I set set -x and noticed the same culprit @david-drinn mentions:

+pyenv:12> pyenv sh-activate --quiet

ron-premier avatar Oct 18 '22 12:10 ron-premier

Try localizing the issue by timing the code that runs at prompt with:

export PS4='+($(date "+%s.%N")) (${BASH_SOURCE:-}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
<reproduce the problem>
set +x

native-api avatar Oct 18 '22 17:10 native-api

If the culprit is a Pyenv subcommand, also export PYENV_DEBUG=1 and replace PS4 in pyenv's source code to the above value to get its timed trace as well.

native-api avatar Oct 18 '22 17:10 native-api