为什么用Perl编写的用于拆分大文件的代码运行速度要快得多,而这些代码运行在Korn Shell中。超过一百万的输入记录。每条记录的前9个字符用于确定记录写入的文件,并在Perl中运行大约4-5分钟。我试图将此代码转换为ksh,它似乎永远运行(小时)。
我真的不知道我做错了什么导致了这个问题。在某些记录中,字符串中嵌入了空白和/或字母字符,因此比较必须是字符串类型比较。关于使我的ksh脚本获得perl性能或为什么不是这样的想法?
我尝试了几种不同的选项,因为ksh / bash在处理变量和比较时往往有很多方法可以做同样或类似的事情。我也没有很好地处理这个非常旧的Perl代码的运行方式。
我的Perl代码:
open(FILEIN,"base.dat") || die "Could not open FILEIN\n.";
open(FILEOUT1,">base1.dat") || die "Could not open FILEOUT1\n.";
open(FILEOUT2,">base2.dat") || die "Could not open FILEOUT2\n.";
open(FILEOUT3,">base3.dat") || die "Could not open FILEOUT3\n.";
$v_break = "518000000";
$v_break2 = "525000000";
#Run until end of file
while (<FILEIN>) {
$v_pcn = substr($_, 0, 9);
if ($v_break gt $v_pcn) {
print FILEOUT1 $_;
}
elsif (($v_pcn ge $v_break) && ($v_pcn lt $v_break2)) {
print FILEOUT2 $_;
}
else
{
print FILEOUT3 $_;
}
} #(<FILEIN>)
close(FILEIN);
close(FILEOUT1);
close(FILEOUT2);
close(FILEOUT3);
我的Shell脚本(ksh):
while read inrec # Read base file until EOF
do # Start work loop
v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn
# v_pcn=${v_pcn/' '/0} # Replace blanks with '0'
if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000"
echo $inrec >> base1.dat # write rec to "base1.dat"
elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000"
echo $inrec >> base3.dat # write rec to "base3.dat"
else # else >= "518000000" & < "525000000"
echo $inrec >> base2.dat # write rec to "base2.dat"
fi
done < base.dat
我希望shell脚本能够生成3个输出文件,这些文件与perl代码生成的文件大致相同,并且时间相同;
输入:
-rw-r--r--. 1 mfadjobt mfadset 2095795750 Feb 13 10:07 base.dat
输出:
-rw-r--r--. 1 mfadjobt mfadset 461650125 Feb 13 10:07 base1.dat
-rw-r--r--. 1 mfadjobt mfadset 519783625 Feb 13 10:07 base2.dat
-rw-r--r--. 1 mfadjobt mfadset 1114362000 Feb 13 10:07 base3.dat
每次有>> filename
时,都会再次打开文件,将指针移动到文件末尾,然后在语句末尾再次关闭文件。最好保持文件打开。
while read inrec # Read base file until EOF
do # Start work loop
v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn
# v_pcn=${v_pcn/' '/0} # Replace blanks with '0'
if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000"
echo $inrec >&3
elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000"
echo $inrec >&4
else # else >= "518000000" & < "525000000"
echo $inrec >&5
fi
done < base.dat 3>> base1.dat 4>> base2.dat 5>> base3.dat
这将打开文件一次,保持指向文件的指针,并应该有助于加快速度。
通常当shell运行缓慢时,这是由于你正在运行的命令,但这里没有任何东西产生子shell,所以我看看下一个最可能的罪魁祸首 - 文件处理。这就是我在这里看到的。
Perl代码被编译成“二进制”表示。然后,该二进制表示由高度优化的解释器执行。
另一方面,Shell脚本
我不确定Korn shell有什么内置,但bash有很多。
执行外部命令很昂贵,因为它涉及至少一个fork()
和一个execve()
系统调用。
通常,当shell脚本非常短时,即Perl编译器的启动成本高于实际代码执行时间时,它将比Perl脚本更快。
简短的回答:将shell脚本转换为等效的Perl脚本时,不要感到惊讶,因为它会运行得更快。
为避免与文件描述符混淆,可以使用for循环。
for inrec in `cat base.dat` # Read base file until EOF
do # Start work loop
v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn
# v_pcn=${v_pcn/' '/0} # Replace blanks with '0'
if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000"
echo $inrec >> base1.dat # write rec to "base1.dat"
elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000"
echo $inrec >> base3.dat # write rec to "base3.dat"
else # else >= "518000000" & < "525000000"
echo $inrec >> base2.dat # write rec to "base2.dat"
fi
done
仅使用bash
测试,但下一个解决方案也适用于一些ksh
版本。首先重新考虑边界。美丽的圆形图让我们可以看到前3个字符。这对所有解决方案都有好处。
使用tee
,您可以写入stdout和文件,但也可以写入不同的进程。
tee < base.dat \
>(grep -E "^([0-4]|50|51[0-7])" > base1.dat) \
>(grep -E "^5(1[89]|2[0-4])" > base2.dat) |
grep -E "^(52[5-9]|5[3-9]|6-9])" > base3.dat