【问题标题】:Bash while read loop extremely slow compared to cat, why?与 cat 相比,Bash while read 循环非常慢,为什么?
【发布时间】:2012-11-25 14:29:55
【问题描述】:

这里有一个简单的测试脚本:

while read LINE; do
        LINECOUNT=$(($LINECOUNT+1))
        if [[ $(($LINECOUNT % 1000)) -eq 0 ]]; then echo $LINECOUNT; fi
done

当我执行cat my450klinefile.txt | myscript 时,CPU 以 100% 的速度锁定,它每秒可以处理大约 1000 行。处理 cat my450klinefile.txt >/dev/null 在半秒内所做的事情大约需要 5 分钟。

有没有一种更有效的方法可以做到这一点。我只需要从标准输入读取一行,计算字节数,然后将其写入命名管道。但即使是这个例子的速度也慢得令人难以置信。

每输入 1Gb 的行,我需要执行一些更复杂的脚本操作(关闭和打开一些数据正在输入的管道)。

【问题讨论】:

  • 除了 bash 脚本和编译工具之间的差异(参见 paxdiablo 的答案)之外,您的比较是不公平的:cat 只是在您的脚本进行一些计算时读取(行数)
  • ((LINECOUNT++))替换LINECOUNT=$(($LINECOUNT+1))
  • 也为了真正的比较你需要从你的脚本中删除条件,现在你的问题看起来像:why my truck uses so much fuel when I trying to transport 20tonns of wood, when i run it without trailer it uses ten times less!
  • 您的示例计算的不是字节而是行
  • 你为什么不使用wc(字数)? wc -l 数行。

标签: linux bash performance shell


【解决方案1】:

while read 这么慢的原因是 shell 需要对每个字节进行系统调用。它无法从管道中读取大缓冲区,因为 shell 不能从输入流中读取多于一行,因此必须将每个字符与换行符进行比较。如果您在 while read 循环上运行 strace,您可以看到此行为。这种行为是可取的,因为它可以可靠地执行以下操作:

while read size; do test "$size" -gt 0 || break; dd bs="$size" count=1 of=file$(( i++ )); done

其中循环内的命令从 shell 读取的同一流中读取。如果 shell 通过读取大缓冲区消耗了大量数据,则内部命令将无法访问该数据。一个不幸的副作用是read 速度慢得离谱。

【讨论】:

    【解决方案2】:

    perl计算每个字符串的字节数:

    perl -p -e '
    use Encode;
    print length(Encode::encode_utf8($_))."\n";$_=""' 
    

    例如:

    dd if=/dev/urandom bs=1M count=100 |
       perl -p -e 'use Encode;print length(Encode::encode_utf8($_))."\n";$_=""' |
       tail
    

    为我工作 7.7Mb/s

    比较使用了多少脚本:

    dd if=/dev/urandom bs=1M count=100 >/dev/null
    

    以 9.1Mb/s 的速度运行

    似乎脚本没那么慢:)

    【讨论】:

      【解决方案3】:

      这是因为在这种情况下,bash 脚本被解释并且没有真正针对速度进行优化。您通常最好使用以下外部工具之一:

      awk 'NR%1000==0{print}' inputFile
      

      与您的“每 1000 行打印一次”示例相匹配。

      如果您想(对于每一行)输出行数(以字符为单位,后跟行本身),并将其通过另一个进程通过管道传输,您也可以这样做:

      awk '{print length($0)" "$0}' inputFile | someOtherProcess
      

      awksedgrepcut 和更强大的 perl 等工具比解释的 shell 脚本更适合这些任务。

      【讨论】:

      • 在输入 1Gb 行之后,我需要执行一些更复杂的操作,关闭几个管道并重新打开它们。 awk 是否能够允许我执行这些更复杂的脚本操作?
      • awk,可能不是,但还有很多其他工具,这就是为什么你应该问你的实际问题而不是一些示例问题:-)
      • 尝试使用 perl 完成该任务
      【解决方案4】:

      不太确定您的脚本应该做什么。因此,这可能不是您问题的答案,而是一个通用提示。

      不要cat您的文件并将其通过管道传输到您的脚本,而是在使用 bash 脚本从文件中读取时这样做:

      while read line    
      do    
          echo $line
      done <file.txt
      

      【讨论】:

      • 我通过管道从标准输入上的 curl 获取输入
      • 不使用read -r 是一个问题,不引用echo "$line" 中的变量更是双重问题。不要使用这个。这是对cat 的极其糟糕的重新实现。
      猜你喜欢
      • 1970-01-01
      • 2018-11-18
      • 2014-02-17
      • 2017-12-17
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      • 2017-05-07
      • 1970-01-01
      相关资源
      最近更新 更多