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重写它来获得很多.尽管循环处理文件名(其速度将得到改善,但可能无关紧要),但对于不完全在内部或外部范围内的每个文件,您可以:
此外,你的尾巴.每次执行此操作时,某些代码都会读取该数据.其中一些线路正在被读取10次或更多次!
cha*_*aos 18
几乎可以肯定,通过在Perl中写入脚本时,通过在传递第二个时间戳时切断文件读取,可以获得巨大的速度.
更普遍的是,是的; bash任何复杂的脚本,除非它是一个真正令人惊奇的魔法,可以通过Perl脚本轻松地获得相同的输入和输出.
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)
根据你拥有的shell代码,多次调用tail/head,我会说Perl 绝对可以更快.C可能更快,但开发时间可能不值得,所以我坚持使用Perl.(我说"可能"因为你可以在Perl中编写shell脚本,而且我已经看到了足够多的东西让人感到畏缩.这显然不会有你想要的速度优势.)
Perl具有更高的启动成本,或者声称具有更高的启动成本.老实说,我从来没有注意到.如果你的选择是用Java做的,那么Perl没有启动成本.与Bash相比,我根本没有注意到.我注意到的是,当我离开调用所有专门的Unix工具时,当你没有其他选择时这些工具很棒,而且只需要在一个进程中完成所有工作,速度就会提高.在Unix上创建新进程的开销并不像在Windows上那样严重,但它仍然不完全可以忽略不计,因为每次都必须重新初始化C运行时库(libC),解析参数,打开文件(可能)在Perl中,当你在列表或其他东西中传递所有内容时,你最终会使用大量内存,但它全部都在内存中,所以它更快.您习惯使用的许多工具都是内置的(正则map/grep表达式),或者在CPAN上的模块中可用.这些的良好组合将使工作轻松完成.
最重要的是避免重新读取文件.这很昂贵.你做了很多次.哎呀,你可以:gzip在open上使用修饰符来直接读取你的gzip文件,节省了另一个传递 - 而且这会更快,因为你从磁盘上读的更少.