Perl比bash更快吗?

Bre*_*nt 11 bash optimization perl comparison performance

我有一个bash脚本,它在两个时间戳之间切出一个日志文件的一部分,但由于文件的大小,运行需要很长时间.

如果我要在Perl中重写脚本,我可以实现显着的速度提升 - 或者我是否必须转向像C这样的东西才能实现这一目标?

#!/bin/bash

if [ $# -ne 3 ]; then
  echo "USAGE $0 <logfile(s)> <from date (epoch)> <to date (epoch)>"
  exit 1
fi

LOGFILES=$1
FROM=$2
TO=$3
rm -f /tmp/getlogs??????
TEMP=`mktemp /tmp/getlogsXXXXXX`

## LOGS NEED TO BE LISTED CHRONOLOGICALLY
ls -lnt $LOGFILES|awk '{print $8}' > $TEMP
LOGFILES=`tac $TEMP`
cp /dev/null $TEMP

findEntry() {
  RETURN=0
  dt=$1
  fil=$2
  ln1=$3
  ln2=$4
  t1=`tail -n+$ln1 $fil|head -n1|cut -c1-15`
  dt1=`date -d "$t1" +%s`
  t2=`tail -n+$ln2 $fil|head -n1|cut -c1-15`
  dt2=`date -d "$t2" +%s`
  if [ $dt -ge $dt2 ]; then
    mid=$dt2
  else
    mid=$(( (($ln2-$ln1)*($dt-$dt1)/($dt2-$dt1))+$ln1 ))
  fi
  t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
  dt3=`date -d "$t3" +%s`
  # finished
  if [ $dt -eq $dt3 ]; then
    # FOUND IT (scroll back to the first match)
    while [ $dt -eq $dt3 ]; do
      mid=$(( $mid-1 ))
      t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
      dt3=`date -d "$t3" +%s`
    done
    RETURN=$(( $mid+1 ))
    return
  fi
  if [ $(( $mid-1 )) -eq $ln1 ] || [ $(( $ln2-1)) -eq $mid ]; then
    # FOUND NEAR IT
    RETURN=$mid
    return
  fi
  # not finished yet
  if [ $dt -lt $dt3 ]; then
    # too high
    findEntry $dt $fil $ln1 $mid
  else
    if [ $dt -ge $dt3 ]; then
      # too low
      findEntry $dt $fil $mid $ln2
    fi
  fi
}

# Check timestamps on logfiles
LOGS=""
for LOG in $LOGFILES; do
  filetime=`ls -ln $LOG|awk '{print $6,$7}'`
  timestamp=`date -d "$filetime" +%s`
  if [ $timestamp -ge $FROM ]; then
    LOGS="$LOGS $LOG"
  fi
done

# Check first and last dates in LOGS to refine further
for LOG in $LOGS; do
    if [ ${LOG%.gz} != $LOG ]; then
      gunzip -c $LOG > $TEMP
    else
      cp $LOG $TEMP
    fi
    t=`head -n1 $TEMP|cut -c1-15`
    FIRST=`date -d "$t" +%s`
    t=`tail -n1 $TEMP|cut -c1-15`
    LAST=`date -d "$t" +%s`
    if [ $TO -lt $FIRST ] || [ $FROM -gt $LAST ]; then
      # This file is entirely out of range
      cp /dev/null $TEMP
    else
      if [ $FROM -le $FIRST ]; then
        if [ $TO -ge $LAST ]; then
          # Entire file is within range
          cat $TEMP
        else
          # Last part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          head -n$RETURN $TEMP
        fi
      else
        if [ $TO -ge $LAST ]; then
          # First part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          tail -n+$RETURN $TEMP
        else
          # range is entirely within this logfile
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n1=$RETURN
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n2=$RETURN
          tail -n+$n1 $TEMP|head -n$(( $n2-$n1 ))
        fi
      fi
    fi
done
rm -f /tmp/getlogs??????
Run Code Online (Sandbox Code Playgroud)

Dan*_*ral 27

Perl比Bash快得多.而且,对于文本操作,除了花时间编写复杂的算法之外,使用Perl实际上可以获得比使用C更好的性能.当然,对于简单的东西,C可以是无与伦比的.

也就是说,如果你的"bash"脚本没有循环,只是调用其他程序,那么就没有任何好处了.例如,如果您的脚本看起来像" cat X | grep Y | tr -f 3-5 | sort | uniq",那么大部分时间都花在cat,grep,tr,sort和uniq上,而不是Bash上.

如果脚本中有任何循环,或者您保存了同一文件的多个读取,您将获得性能.

你说你在文件上的两个时间戳之间剪切东西.假设您的Bash脚本如下所示:

LINE1=`grep -n TIMESTAMP1 filename | head -1 | cut -d ':' -f 1`
LINE2=`grep -n TIMESTAMP2 filename | head -1 | cut -d ':' -f 1`
tail +$LINE1 filename | head -$(($LINE2-$LINE1))
Run Code Online (Sandbox Code Playgroud)

然后你将获得性能,因为你正在读取整个文件三次:每个命令出现"filename"一次.在Perl中,你会做这样的事情:

my $state = 0;
while(<>) {
  exit if /TIMESTAMP2/;
  print $_ if $state == 1;
  $state = 1 if /TIMESTAMP1/;
}
Run Code Online (Sandbox Code Playgroud)

这将只读取一次文件,并且一旦读取TIMESTAMP2也将停止.由于您正在处理多个文件,因此您将使用"last"或"break"而不是"exit",以便脚本可以继续处理文件.

无论如何,看到你的剧本我很肯定你会通过用Perl重写它来获得很多.尽管循环处理文件名(其速度将得到改善,但可能无关紧要),但对于不完全在内部或外部范围内的每个文件,您可以:

  1. 读取整个文件来计算行数!
  2. 在文件上做多个尾部
  3. 再次以"head"或"tail"文件结束

此外,你的尾巴.每次执行此操作时,某些代码都会读取该数据.其中一些线路正在被读取10次或更多次!

  • 顺便说一句,这是猫的不必要的使用. (9认同)
  • 就个人而言,出于风格原因,我总是对猫进行"不必要的"使用 - 我喜欢让数据始终从左向右流动,而不是从右到左然后再向右流动,因为在grep中Y <X | foo> Z. (6认同)
  • @Bdonlan:您可以通过编写`<X grep Y |来解决从左到右的问题 foo> Z`.如果您认为可能需要单独重做grep命令几次(`<file grep item`),并且您希望在调用上一个命令时将光标放在要编辑的点上,这将特别有用用箭头键.见这里:http://sial.org/howto/shell/useless-cat/ (5认同)
  • Cat非常轻,所以去除它是过早的优化.使用cat具有额外的优点,可以更容易地更改以下步骤的订购者.此外,如果需要,它可以更容易地用gzcat替换.最后,谁在乎呢?我没有暗示它. (3认同)

cha*_*aos 18

几乎可以肯定,通过在Perl中写入脚本时,通过在传递第二个时间戳时切断文件读取,可以获得巨大的速度.

更普遍的是,是的; bash任何复杂的脚本,除非它是一个真正令人惊奇的魔法,可以通过Perl脚本轻松地获得相同的输入和输出.

  • 就在.在大多数情况下,不同语言的表现不是因为语言的实现; 但是因为它可以让你应用更好的算法. (8认同)

Sin*_*nür 12

根据布伦特的评论更新了脚本:这个是未经测试的.

#!/usr/bin/perl

use strict;
use warnings;

my %months = (
    jan => 1, feb => 2,  mar => 3,  apr => 4,
    may => 5, jun => 6,  jul => 7,  aug => 8,
    sep => 9, oct => 10, nov => 11, dec => 12,
);

while ( my $line = <> ) {
    my $ts = substr $line, 0, 15;
    next if parse_date($ts) lt '0201100543';
    last if parse_date($ts) gt '0715123456';
    print $line;
}

sub parse_date {
    my ($month, $day, $time) = split ' ', $_[0];
    my ($hour, $min, $sec) = split /:/, $time;
    return sprintf(
        '%2.2d%2.2d%2.2d%2.2d%2.2d',
        $months{lc $month}, $day,
        $hour, $min, $sec,
    );
}


__END__
Run Code Online (Sandbox Code Playgroud)

以前的答案供参考:文件的格式是什么?这是一个简短的脚本,它假定第一列是时间戳,并且只打印具有特定范围内的时间戳的行.它还假定时间戳已排序.在我的系统上,花费大约一秒钟来过滤掉一百万行中的900,000行:

#!/usr/bin/perl

use strict;
use warnings;

while ( <> ) {
    my ($ts) = split;
    next if $ts < 1247672719;
    last if $ts > 1252172093;
    print $ts, "\n";
}

__END__
Run Code Online (Sandbox Code Playgroud)


Tan*_*lus 5

根据你拥有的shell代码,多次调用tail/head,我会说Perl 绝对可以更快.C可能更快,但开发时间可能不值得,所以我坚持使用Perl.(我说"可能"因为你可以在Perl中编写shell脚本,而且我已经看到了足够多的东西让人感到畏缩.这显然不会有你想要的速度优势.)

Perl具有更高的启动成本,或者声称具有更高的启动成本.老实说,我从来没有注意到.如果你的选择是用Java做的,那么Perl没有启动成本.与Bash相比,我根本没有注意到.我注意到的是,当我离开调用所有专门的Unix工具时,当你没有其他选择时这些工具很棒,而且只需要在一个进程中完成所有工作,速度就会提高.在Unix上创建新进程的开销并不像在Windows上那样严重,但它仍然不完全可以忽略不计,因为每次都必须重新初始化C运行时库(libC),解析参数,打开文件(可能)在Perl中,当你在列表或其他东西中传递所有内容时,你最终会使用大量内存,但它全部都在内存中,所以它更快.您习惯使用的许多工具都是内置的(正则map/grep表达式),或者在CPAN上的模块中可用.这些的良好组合将使工作轻松完成.

最重要的是避免重新读取文件.这很昂贵.你做了很多次.哎呀,你可以:gzip在open上使用修饰符来直接读取你的gzip文件,节省了另一个传递 - 而且这会更快,因为你从磁盘上读的更少.