pyenv-virtualenv
pyenv-virtualenv copied to clipboard
Slow shell performance after running pyenv virtualenv-init
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?
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.
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
running into the same problem too, on Ubuntu 16.04.
@Honghe Does your laptop run CarbonBlack? That was the root cause of my performance issue.
@cmcginty no running CarbonBlack ;(
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.
Experiencing this^ too.
Similar problem on Debian 9 with zsh. Removing eval "$(pyenv virtualenv-init -)"
from ~/.zshrc
fixes it.
any news ?
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.
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.
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
?
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.
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
- Unload hook func
_pyenv_virtualenv_hook
- Never use
pyenv activate/deactivate
, cause they're broken without the above hook. Stick withpyenv 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
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.
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.
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.
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.
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
inPS4
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 withtime
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.
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"
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.
Same issue here on Raspberry Pi4
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:
Nonetheless, after adding it to my .zshrc, I obtain from the same tool:
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)
********************************************************************
Thanks @monopoler08 your solution did not work for me, I did not have a precwd
but chpwd
event. I changed your command with:
eval "$(pyenv virtualenv-init - | sed s/precmd/chpwd/g)"
And it worked perfectly.
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.
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
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
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.
pyenv sh-activate
This fixed the issue for me, thanks