Bash脚本;优化加工速度


10

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

  • 例如,写循环比命令行更方便,但是系统处理起来也更快吗?例:

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

    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'
      

2
在大多数情况下,我宁愿可读性胜于速度。
伯恩哈德

1
a)是的,使用for循环会产生开销,但没关系b)尝试最小化外部命令,仅从bash中替换内容。而且,如果您必须优化Shell脚本,那么您在做错事,应该考虑使用具有更好的事件探查器支持的语言。
Ulrich Dangel

正则表达式通常比任何其他选择在效率上都不那么有效,因此,如果您发现一种不做任何事情的方法(例如,剪切),请使用它。当然,如果您的阅读能力仅限于正则表达式,则可能会影响可读性;)
goldilocks 2013年

Answers:


8

Shell不会对收到的代码进行任何重组,它只是一行一行地被解释(在命令解释器中没有其他意义)。Shell花费的大部分时间都用于词法分析/解析/启动所调用的程序。

对于简单的操作(如问题末尾的示例中的字符串操作),如果加载程序的时间没有淹没任何微小的速度差异,我会感到惊讶。

这个故事的寓意是,如果您确实需要更高的速度,那么使用(半)编译语言(例如Perl或Python)会更好,因为这种语言起步更快,您可以在其中编写许多直接提到的操作不必调用外部程序,并且可以选择调用外部程序或调用优化的C(或任何其他形式)模块来完成许多工作。这就是为什么在Fedora中用Python编写“系统管理糖”(本质上是GUI)的原因:可以轻松地添加漂亮的GUI,对于此类应用程序足够快,可以直接访问系统调用。如果那还不够快,请使用C ++或C。

但是不要去那里,除非您可以证明性能的提高值得在灵活性和开发时间上付出损失。Shell脚本读起来还不错,但是当我记得曾经尝试解密的一些用于安装Ultrix的脚本时,我感到不寒而栗。我放弃了,已经应用了太多的“ shell脚本优化”。


1
+1,很多人会认为,使用python或perl vs. shell之类的东西可能会增加灵活性和开发时间,而不是损失。我会说,仅在必要时使用shell脚本,否则您正在做的事情涉及大量的shell特定命令。
goldilocks 2013年

22

优化的第一条规则是:不要优化。首先测试。如果测试表明您的程序太慢,请寻找可能的优化方法。

确保唯一的方法是对用例进行基准测试。有一些通用规则,但是它们仅适用于典型应用程序中的典型数据量。

在某些特定情况下可能适用或不适用的一些一般规则:

  • 对于外壳程序中的内部处理,ATT ksh是最快的。如果您进行了大量的字符串操作,请使用ATT ksh。短跑排名第二;bash,pdksh和zsh落后。
  • 如果您每次需要频繁调用Shell来执行非常短的任务,则dash会因为其启动时间短而获胜。
  • 启动外部流程会花费时间,因此拥有一个具有复杂片段的管道要比循环中的管道更快。
  • echo $foo比慢echo "$foo",因为没有双引号,它会分解$foo成单词并将每个单词解释为文件名通配符模式。更重要的是,很少需要分裂和浮球行为。因此,请记住始终在变量替换和命令替换两边加上双引号:"$foo""$(foo)"
  • 专用工具往往会胜过通用工具。例如,诸如cuthead可以用进行仿真的工具sed,但sed会变慢,awk甚至会变慢。Shell字符串处理速度很慢,但是对于短字符串而言,它在很大程度上胜过调用外部程序。
  • 诸如Perl,Python和Ruby之类的更高级的语言通常可以让您编写更快的算法,但是它们的启动时间要长得多,因此对于大量数据的性能而言,它们仅是值得的。
  • 至少在Linux上,管道往往比临时文件快。
  • Shell脚本的大多数用法都是围绕I / O绑定的进程,因此CPU消耗无关紧要。

在shell脚本中很少会关注性能。上面的清单仅供参考;在大多数情况下,使用“慢速”方法是完全可以的,因为差异通常仅为百分之一。

通常,shell脚本的目的是快速完成任务。您必须从优化中获得很多收益,以证明花费额外的时间编写脚本。


2
虽然pythonruby启动速度肯定较慢,但至少在我的系统上,perl启动速度与bash或一样快ksh。GNU awk明显比GNU sed慢,尤其是在utf-8语言环境中,但并非所有awk和所有sed都是如此。ksh93>破折号> pdksh> zsh> bash并不总是那么清晰。有些炮弹在某些方面比其他炮弹更胜一筹,而获胜者并不总是相同的。
斯特凡Chazelas

2
关于“您必须从...获得很多收益”:如果“您”包括用户库,则为true。使用流行的Linux软件包中的Shell脚本,用户通常比匆忙的程序员节省的时间多了几个数量级。
agc

2

我们将在上面的globing示例中进行扩展,以说明Shell脚本解释器的一些性能特征。比较本例bashdash解释器(其中为30,000个文件中的每一个生成一个进程),该破折号可以以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系统上丰富的实用程序集中的现有逻辑。

PádraigBrady从常见的shell脚本错误中偷走了。


1
一个通用规则:文件描述符处理也要花费很多,因此要减少它们的数量。而不是for i in *; do wc -l "$i">/dev/null; done更好for i in *; do wc -l "$i"; done>/dev/null
manatwork

@manatwork它也将使timecmd的输出为空
Rahul Patil

@manatwork很好...现在也请给我输出,而无需调用wc -l,请检查我是否已在发布您的输出后更新了
Rahul Patil

好吧,以前的测量是在较小的目录上进行的。现在,我创建了一个包含30000个文件的文件,并重复了测试:pastebin.com/pCV6QKp2
manatwork 2013年

这些基准测试无法考虑每个shell的不同开始时间。每个外壳完成基准测试会更好。
gc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.