bash 脚本;优化处理速度

bash 脚本;优化处理速度

我想知道是否有优化 Bash 脚本的一般准则。

  • 例如,它更方便的编写循环而不是命令行,但它也是处理速度更快对于系统?例子:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
    
  • 有时人们对同一问题提出不同的解决方案。例如,sedcutawkecho都能够从字符串中删除数字。我想知道你是否可以说,如果你使用以下代码,数字越少,速度就越快:

    1. 相同的命令,例如

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
      
    2. 不同的命令,例如

      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 脚本解释器的一些性能特征。比较此示例中的bashdash解释器(其中为 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(),则前者会更快
  • 对于字符串操作,请使用变量扩展${...}以支持其他替代方案,例如调用外部命令(cuttr

  • 处理文本文件时,如果可能,请避免逐行读取变量,而应在整个文件上使用外部命令,例如sed, tr, 。cut

    • 如果您需要将文本文件读取到变量来处理数据,包括过滤数据(减少信息),请尝试使用外部命令对整个文件进行过滤,然后将过滤后的(减少的)数据读入变量。
  • 如果使用正则表达式,请仔细编写,例如避免回溯。

    • 写得不好的 regex-es 的性能有时可能比同等的 regex 差 100 倍甚至更多。

相关内容