我想知道是否有优化 Bash 脚本的一般准则。
例如,它更方便的编写循环而不是命令行,但它也是处理速度更快对于系统?例子:
for i in a b c; do echo $i; done echo a echo b echo c
有时人们对同一问题提出不同的解决方案。例如,
sed
、cut
、awk
和echo
都能够从字符串中删除数字。我想知道你是否可以说,如果你使用以下代码,数字越少,速度就越快:相同的命令,例如
STRING=abc.def echo ${STRING} | sed 's/.def//g' echo ${STRING} | sed '$s/....$//'
不同的命令,例如
STRING=abc.def echo ${STRING} | cut -d . -f 1 echo ${STRING} | sed 's/.def//g'
答案1
优化的第一条规则是:不优化。先测试一下。如果测试表明您的程序太慢,请寻找可能的优化。
唯一确定的方法是针对您的用例进行基准测试。有一些通用规则,但它们仅适用于典型应用程序中的典型数据量。
一些一般规则在任何特定情况下可能正确也可能不正确:
- 对于 shell 中的内部处理,ATT ksh 是最快的。如果您进行大量字符串操作,请使用 ATT ksh。达世币位居第二; bash、pdksh 和 zsh 落后。
- 如果您需要频繁调用 shell 来每次执行很短的任务,那么 dash 会因其启动时间短而胜出。
- 启动外部进程需要时间,因此拥有一个包含复杂部分的管道比循环中的管道更快。
echo $foo
比 慢echo "$foo"
,因为没有双引号,它会分成$foo
单词并将每个单词解释为文件名通配符模式。更重要的是,很少需要这种分裂和通配行为。因此请记住始终在变量替换和命令替换两边加上双引号:"$foo"
,"$(foo)"
。- 专用工具往往胜过通用工具。例如,类似
cut
或 之类的工具head
可以用 来模拟sed
,但sed
会更慢,awk
甚至会更慢。 Shell 字符串处理速度很慢,但对于短字符串,它在很大程度上胜过调用外部程序。 - Perl、Python 和 Ruby 等更高级的语言通常可以让您编写更快的算法,但它们的启动时间明显更长,因此只有在处理大量数据时才值得这样做。
- 至少在 Linux 上,管道往往比临时文件更快。
- shell 脚本的大多数用途都是围绕 I/O 密集型进程,因此 CPU 消耗并不重要。
shell 脚本中很少会考虑性能问题。上面的列表纯粹是指示性的;在大多数情况下,使用“慢速”方法是完全可以的,因为差异通常只有百分之几。
通常 shell 脚本的目的是快速完成某件事。您必须从优化中获益匪浅,才能证明花费额外的时间编写脚本是合理的。
答案2
Shell 不会对它们收到的代码进行任何重组,它只是一行接一行地进行解释(在命令解释器中没有其他任何意义)。 shell 花费的大部分时间都花在词法分析/解析/启动所调用的程序上。
对于简单的操作(例如问题末尾示例中的修改字符串的操作),如果加载程序的时间没有淹没任何微小的速度差异,我会感到惊讶。
这个故事的寓意是,如果您确实需要更快的速度,那么最好使用像 Perl 或 Python 这样的(半)编译语言,它们一开始运行速度更快,您可以在其中编写许多直接提到的操作并且不必调用外部程序,并且可以选择调用外部程序或调用优化的 C(或其他)模块来完成大部分工作。这就是为什么在 Fedora 中,“系统管理糖”(本质上是 GUI)是用 Python 编写的:不需要太多努力就可以添加一个漂亮的 GUI,对于此类应用程序来说足够快,可以直接访问系统调用。如果这还不够速度,请选择 C++ 或 C。
但不要去那里,除非你可以证明性能的提升值得灵活性和开发时间的损失。 Shell 脚本读起来还不错,但是当我想起曾经试图破译的一些用于安装 Ultrix 的脚本时,我不寒而栗。我放弃了,应用了太多“shell 脚本优化”。
答案3
我们将在此扩展上面的通配示例,以说明 shell 脚本解释器的一些性能特征。比较此示例中的
bash
和dash
解释器(其中为 30,000 个文件中的每一个生成一个进程),表明 dash 分叉wc
进程的速度几乎是bash
bash-4.2$ time dash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real 0m1.238s
user 0m0.309s
sys 0m0.815s
bash-4.2$ time bash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real 0m1.422s
user 0m0.349s
sys 0m0.940s
通过不调用进程来比较基本循环速度
wc
,表明 dash 的循环速度快了近 6 倍!$ time bash -c 'for i in *; do echo "$i">/dev/null; done' real 0m1.715s user 0m1.459s sys 0m0.252s $ time dash -c 'for i in *; do echo "$i">/dev/null; done' real 0m0.375s user 0m0.169s sys 0m0.203s
正如前面所演示的,循环在任一 shell 中仍然相对较慢,因此为了可扩展性,我们应该尝试使用更多功能技术,以便在编译过程中执行迭代。
$ time find -type f -print0 | wc -l --files0-from=- | tail -n1 30000 total real 0m0.299s user 0m0.072s sys 0m0.221s
以上是迄今为止最有效的解决方案,并很好地说明了这一点:人们应该在 shell 脚本中尽可能少地执行操作,并且旨在使用它来连接 UNIX 系统上可用的丰富实用程序集中的现有逻辑。
被盗自常见的 shell 脚本错误作者:帕德莱格·布雷迪。
答案4
我的快速看法:
避免使用子壳。
- 请记住,
{ }
(无子外壳)通常/有时可以用来代替( )
(子外壳)
- 请记住,
使用 shell 内置函数有利于外部命令。
date()
例如,通过调用 shell 函数而不是 eg来获取日期/usr/bin/date
,对于printf
使用 shell 变量有利于调用 shell 内置命令或命令。
- 例如,要获取当前时间,如果您可以使用查询
$SECONDS
而不是调用date()
,则前者会更快
- 例如,要获取当前时间,如果您可以使用查询
对于字符串操作,请使用变量扩展
${...}
以支持其他替代方案,例如调用外部命令(cut
等tr
)处理文本文件时,如果可能,请避免逐行读取变量,而应在整个文件上使用外部命令,例如
sed
,tr
, 。cut
- 如果您需要将文本文件读取到变量来处理数据,包括过滤数据(减少信息),请尝试使用外部命令对整个文件进行过滤,然后将过滤后的(减少的)数据读入变量。
如果使用正则表达式,请仔细编写,例如避免回溯。
- 写得不好的 regex-es 的性能有时可能比同等的 regex 差 100 倍甚至更多。