目前 bash 需要大约 2 秒才能加载。我已经用-x
flag 运行了 bash,我看到了输出,似乎 PATH 在 cygwin 中被加载了很多次。有趣的是我在 linux 环境中使用相同的文件,但它工作正常,没有重新加载问题。以下是否会导致问题?
if [ `uname -o` = "Cygwin" ]; then
....
fi
目前 bash 需要大约 2 秒才能加载。我已经用-x
flag 运行了 bash,我看到了输出,似乎 PATH 在 cygwin 中被加载了很多次。有趣的是我在 linux 环境中使用相同的文件,但它工作正常,没有重新加载问题。以下是否会导致问题?
if [ `uname -o` = "Cygwin" ]; then
....
fi
正如您在回答中指出的那样,问题在于 Cygwin 的 bash-completion 包。快速简单的解决方法是禁用 bash-completion,正确的方法是运行 Cygwin 的 setup.exe(如果需要,请再次下载)并选择卸载该软件包。
更长的解决方案是处理文件/etc/bash_completion.d
并禁用您不需要的文件。在我的系统上,减慢 Bash 加载时间的最大罪魁祸首(mailman、shadow、dsniff 和 e2fsprogs)都没有做任何事情,因为没有安装它们被创建来完成的工具。
如果您将文件重命名/etc/bash_completion.d
为具有.bak
扩展名,它将停止加载该脚本。以这种方式在我的一个系统上禁用了除选定的 37 个脚本之外的所有脚本后,我将 bash_completion 加载的平均时间缩短了 95%(6.5 秒到 0.3 秒)。
就我而言,那是 Windows 域控制器。我这样做是为了找到问题:
我从一个简单的 windows 开始,cmd.exe
然后输入:
c:\cygwin\bin\strace.exe c:\cygwin\bin\bash
就我而言,我注意到以下顺序:
218 12134 [main] bash 11304 transport_layer_pipes::connect: Try to connect to named pipe: \\.\pipe\cygwin-c5e39b7a9d22bafb-lpc
45 12179 [main] bash 11304 transport_layer_pipes::connect: Error opening the pipe (2)
39 12218 [main] bash 11304 client_request::make_request: cygserver un-available
1404719 1416937 [main] bash 11304 pwdgrp::fetch_account_from_windows: line: <CENSORED_GROUP_ID_#1>
495 1417432 [main] bash 11304 pwdgrp::fetch_account_from_windows: line: <CENSORED_GROUP_ID_#2>
380 1417812 [main] bash 11304 pwdgrp::fetch_account_from_windows: line: <CENSORED_GROUP_ID_#3>
etc...
关键是确定client_request::make_request: cygserver un-available
线路。您可以看到,在那之后,cygwin 如何尝试从 Windows 中获取每个组,并且执行时间变得疯狂。
一个快速的谷歌揭示了 acygserver
是什么:
https ://cygwin.com/cygwin-ug-net/using-cygserver.html
Cygserver 是一个旨在作为后台服务运行的程序。它为 Cygwin 应用程序提供需要安全仲裁或需要在没有其他 cygwin 应用程序运行时持续存在的服务。
解决方案是,运行cygserver-config
然后net start cygserver
启动 Windows 服务。Cygwin 的启动时间在那之后显着下降。
所有答案都指的是旧版本的 bash_completion,与最近的bash_completion
.
现代 bash_completion 默认将大部分完成文件移动到/usr/share/bash-completion/completions
,通过运行检查系统上的路径
# pkg-config --variable=completionsdir bash-completion
/usr/share/bash-completion/completions
那里有很多文件,每个命令一个,但这不是问题,因为它们是在您第一次对每个命令使用完成时按需加载的。/etc/bash_completion.d
仍然支持旧版本以实现兼容性,并且在bash_completion
启动时会加载其中的所有文件。
# pkg-config --variable=compatdir bash-completion
/etc/bash_completion.d
使用此脚本检查旧目录中是否有任何陈旧文件。
#!/bin/sh
COMPLETIONS_DIR="$(pkg-config --variable=completionsdir bash-completion)"
COMPAT_DIR="$(pkg-config --variable=compatdir bash-completion)"
for file in "${COMPLETIONS_DIR}"/*; do
file="${COMPAT_DIR}/${file#${COMPLETIONS_DIR}/}"
[ -f "$file" ] && printf '%s\n' $file
done
它打印 compat 目录中的文件列表,这些文件也存在于较新的(按需)完成目录中。除非您有特定理由保留其中一些文件,否则请查看、备份和删除所有这些文件。
结果,兼容目录应该大部分是空的。
现在,对于最有趣的部分 - 检查bash
启动缓慢的原因。如果您只是运行bash
,它将启动一个非登录的交互式shell - 这是 Cygwin 源代码上的一个/etc/bash.bashrc
,然后是~/.bashrc
. 这很可能不包括 bash 完成,除非您从 rc 文件之一获取它。如果您运行bash -l
( bash --login
)、启动Cygwin Terminal
(取决于您的cygwin.bat
) 或通过 SSH 登录,它将启动一个登录的交互式shell - 它将获取/etc/profile
、~/.bash_profile
和上述 rc 文件。/etc/profile
脚本.sh
本身在/etc/profile.d
.
您可以检查每个文件需要多长时间才能获取。在以下位置找到此代码/etc/profile
:
for file in /etc/profile.d/*.$1; do
[ -e "${file}" ] && . "${file}"
done
备份它,然后用这个替换它:
for file in /etc/profile.d/*.$1; do
TIMEFORMAT="%3lR ${file}"
[ -e "${file}" ] && time . "${file}"
done
开始bash
,您将看到每个文件花费了多长时间。调查需要大量时间的文件。就我而言,它是bash_completion.sh
and fzf.sh
(fzf 是模糊查找器,对 bash_completion 的一个非常好的补充)。现在的选择是禁用它或进一步调查。由于我想继续fzf
在 bash 中使用快捷方式,因此我进行了调查,找到了减速的根源,对其进行了优化,并将我的补丁提交给了 fzf 的 repo(希望它会被接受)。
现在是最大的时间花费者 - bash_completion.sh
。基本上就是脚本来源/usr/share/bash-completion/bash_completion
。我备份了那个文件,然后编辑了它。在最后一页有一个for
循环,它获取compat
dir -中的所有文件/etc/bash_completion.d
。再次,我添加了TIMEFORMAT
and time
,并查看了导致启动缓慢的脚本。它是zzz-fzf
(fzf
包)。我调查并发现一个子shell($()
)在循环中多次执行for
,重写了该部分而不使用子shell,使脚本快速运行。我已经将我的补丁提交给了 fzf 的仓库。
所有这些减速的最大原因是:fork
不受 Windows 进程模型的支持,Cygwin 在模拟它方面做得很好,但与真正的 UNIX 相比,它的速度慢得令人痛苦。一个 subshell 或一个只做很少工作的管道将大部分的执行时间花在fork
-ing 上。例如,比较(我的 Cygwin 上的time echo msg
0.000 秒)与time echo $(echo msg)
(我的 Cygwin 上的 0.042 秒)的执行时间——白天和黑夜。这echo
命令本身不会花费太多时间来执行,但是创建一个子shell 是非常昂贵的。在我的 Linux 系统上,这些命令分别需要 0.000 秒和 0.001 秒。Cygwin 的许多软件包都是由使用 Linux 或其他 UNIX 的人开发的,并且可以在未经修改的情况下在 Cygwin 上运行。因此,这些开发人员自然而然地可以在方便的地方随意使用子shell、管道和其他功能,因为他们不会感觉到对他们的系统有任何显着的性能影响,但是在 Cygwin 上,这些 shell 脚本的运行速度可能会慢几十到几百倍。
底线,如果 shell 脚本在 Cygwin 中运行缓慢 - 尝试定位fork
调用源并重写脚本以尽可能消除它们。例如cmd="$(printf "$1" "$2")"
(使用一个 fork 作为 subshell)可以替换为printf -v cmd "$1" "$2"
.
男孩,它出来的时间很长。读到这里的人都是真正的英雄。谢谢 :)
我知道这是一个旧线程,但是在本周全新安装 Cygwin 后,我仍然遇到这个问题。
我没有手工挑选所有的 bash_completion 文件,而是使用这一行来实现 @me_and 的方法来处理我机器上没有安装的任何东西。这对我来说显着减少了 bash 的启动时间。
在/etc/bash_completion.d
中,执行以下操作:
for i in $(ls|grep -v /); do type $i >/dev/null 2>&1 || mv $i $i.bak; done
PATH
与原始问题有关的旧线程的新答案。
大多数其他答案都与 bash 启动有关。如果您在bash -i
shell 中运行时看到加载时间很慢,那么这些可能适用。
在我的情况下,bash -i
运行速度很快,但每当我打开一个新的 shell(无论是在终端中还是在 xterm 中),都需要很长时间。如果bash -l
是需要很长时间,这意味着它是登录时间。
在https://cygwin.com/faq/faq.html#faq.using.startup-slow的 Cygwin 常见问题解答中有一些方法,但它们对我不起作用。
最初的海报询问了PATH
他诊断使用的bash -x
. 我也发现虽然bash -i
速度很快,但速度bash -xl
很慢,并且显示了很多关于PATH
.
有一个长得可笑的Windows PATH
,登录过程一直在运行程序并在整个程序中搜索PATH
正确的程序。
我的解决方案:编辑 WindowsPATH
以删除任何多余的东西。我不确定我删除的哪个部分起到了作用,但登录 shell 启动时间从 6 秒缩短到了 1 秒以下。
YMMV。
我的回答和上面的npe一样。但是,由于我刚刚加入,我无法评论甚至投票!我希望这篇文章不会被删除,因为它为任何寻找相同问题答案的人提供了保证。
npe 的解决方案对我有用。只有一个警告 - 在我充分利用它之前,我必须关闭所有 cygwin 进程。这包括运行 cygwin 服务,如 sshd,以及我从登录脚本启动的 ssh-agent。在此之前,cygwin 终端的窗口会立即出现,但会在出现提示之前挂起几秒钟。关闭窗口后它挂了几秒钟。在我杀死所有进程并启动 cygserver 服务之后(顺便说一句,我更喜欢使用 Cygwin 方式 - 'cygrunsrv -S cygserver',而不是 'net start cygserver';我不知道它是否有任何实际区别)它立即启动。所以再次感谢npe!
我在一个设置非常复杂的公司网络上,这似乎真的扼杀了 cygwin 的启动时间。与 npe 的回答相关,我还必须遵循此处列出的一些步骤:https ://cygwin.com/faq/faq.html#faq.using.startup-slow
AD 客户端系统的另一个原因是 DC 响应缓慢,通常在具有远程 DC 访问的配置中观察到。Cygwin DLL 查询有关您所在的每个组的信息,以便在启动时填充本地缓存。您可以通过将自己的信息缓存在本地文件中来稍微加快此过程。在对 /etc 具有写访问权限的 Cygwin 终端中运行这些命令:
getent passwd $(id -u) > /etc/passwd
getent group $(id -G) > /etc/group
另外,设置
/etc/nsswitch.conf
如下:
passwd: files db
group: files db
这将限制 Cygwin 联系 AD 域控制器 (DC) 的需要,同时仍允许从 DC 检索其他信息,例如在列出远程目录时。
在这样做加上启动 cygserver 之后,我的 cygwin 启动时间显着下降。
正如上面有人提到的,一个可能的问题是 PATH 环境变量包含太多路径,cygwin 将搜索所有这些。我更喜欢直接编辑 /etc/profile,只需将 PATH 变量覆盖到 cygwin 相关路径,例如PATH="/usr/local/bin:/usr/bin"
. 如果需要,请添加其他路径。
我编写了一个名为“minimizecompletion”的 Bash 函数,用于停用不需要的完成脚本。
完成脚本可以添加多个完成规范或具有用于 shell 构建的完成规范,因此将脚本名称与 $PATH 中找到的可执行文件进行比较是不够的。
我的解决方案是删除所有加载的完成规范,加载完成脚本并检查它是否添加了新的完成规范。根据这一点,通过将 .bak 添加到脚本文件名来禁用它,或者通过删除 .bak 来激活它。对 /etc/bash_completion.d 中的所有 182 个脚本执行此操作会导致 36 个活动和 146 个非活动完成脚本将 Bash 启动时间减少 50%(但应该清楚这取决于已安装的包)。
该函数还检查未激活的完成脚本,以便在新安装的 Cygwin 软件包需要它们时激活它们。可以使用激活所有脚本的参数 -a 撤消所有更改。
# Enable or disable global completion scripts for speeding up Bash start.
#
# Script files in directory '/etc/bash_completion.d' are inactived
# by adding the suffix '.bak' to the file name; they are activated by
# removing the suffix '.bak'. After processing all completion scripts
# are reloaded by calling '/etc/bash_completion'
#
# usage: [-a]
# -a activate all completion scripts
# output: statistic about total number of completion scripts, number of
# activated, and number of inactivated completion scripts; the
# statistic for active and inactive completion scripts can be
# wrong when 'mv' errors occure
# return: 0 all scripts are checked and completion loading was
# successful; this does not mean that every call of 'mv'
# for adding or removing the suffix was successful
# 66 the completion directory or loading script is missing
#
minimizecompletion() {
local arg_activate_all=${1-}
local completion_load=/etc/bash_completion
local completion_dir=/etc/bash_completion.d
(
# Needed for executing completion scripts.
#
local UNAME='Cygwin'
local USERLAND='Cygwin'
shopt -s extglob progcomp
have() {
unset -v have
local PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin"
type -- "$1" &>/dev/null && have='yes'
}
# Print initial statistic.
#
printf 'Completion scripts status:\n'
printf ' total: 0\n'
printf ' active: 0\n'
printf ' inactive: 0\n'
printf 'Completion scripts changed:\n'
printf ' activated: 0\n'
printf ' inactivated: 0\n'
# Test the effect of execution for every completion script by
# checking the number of completion specifications after execution.
# The completion scripts are renamed depending on the result to
# activate or inactivate them.
#
local completions total=0 active=0 inactive=0 activated=0 inactivated=0
while IFS= read -r -d '' f; do
((++total))
if [[ $arg_activate_all == -a ]]; then
[[ $f == *.bak ]] && mv -- "$f" "${f%.bak}" && ((++activated))
((++active))
else
complete -r
source -- "$f"
completions=$(complete | wc -l)
if (( $completions > 0 )); then
[[ $f == *.bak ]] && mv -- "$f" "${f%.bak}" && ((++activated))
((++active))
else
[[ $f != *.bak ]] && mv -- "$f" "$f.bak" && ((++inactivated))
((++inactive))
fi
fi
# Update statistic.
#
printf '\r\e[6A\e[15C%s' "$total"
printf '\r\e[1B\e[15C%s' "$active"
printf '\r\e[1B\e[15C%s' "$inactive"
printf '\r\e[2B\e[15C%s' "$activated"
printf '\r\e[1B\e[15C%s' "$inactivated"
printf '\r\e[1B'
done < <(find "$completion_dir" -maxdepth 1 -type f -print0)
if [[ $arg_activate_all != -a ]]; then
printf '\nYou can activate all scripts with %s.\n' "'$FUNCNAME -a'"
fi
if ! [[ -f $completion_load && -r $completion_load ]]; then
printf 'Cannot reload completions, missing %s.\n' \
"'$completion_load'" >&2
return 66
fi
)
complete -r
source -- "$completion_load"
}
这是一个示例输出和结果时间:
$ minimizecompletion -a
Completion scripts status:
total: 182
active: 182
inactive: 0
Completion scripts changed:
activated: 146
inactivated: 0
$ time bash -lic exit
logout
real 0m0.798s
user 0m0.263s
sys 0m0.341s
$ time minimizecompletion
Completion scripts status:
total: 182
active: 36
inactive: 146
Completion scripts changed:
activated: 0
inactivated: 146
You can activate all scripts with 'minimizecompletion -a'.
real 0m17.101s
user 0m1.841s
sys 0m6.260s
$ time bash -lic exit
logout
real 0m0.422s
user 0m0.092s
sys 0m0.154s