我最近注意到,我在Perl中编写的用于子10MB文件的快速脚本已被修改,重新任务并在40MB +文本文件中使用,在批处理环境中存在严重的性能问题。
当遇到大型文本文件时,每次运行的作业已经运行了大约12个小时,我想知道如何提高代码的性能?我应该将文件粘贴到内存中,如果我这样做,将会破坏作业对文件中行号的依赖。任何建设性的想法都会非常感激,我知道这个工作循环遍历文件太多次但是如何减少它?
#!/usr/bin/perl
use strict;
use warnings;
my $filename = "$ARGV[0]"; # This is needed for regular batch use
my $cancfile = "$ARGV[1]"; # This is needed for regular batch use
my @num =();
open(FILE, "<", "$filename") || error("Cannot open file ($!)");
while (<FILE>)
{
push (@num, $.) if (/^P\|/)
}
close FILE;
my $start;
my $end;
my $loop = scalar(@num);
my $counter =1;
my $test;
open (OUTCANC, ">>$cancfile") || error ("Could not open file: ($!)");
#Lets print out the letters minus the CANCEL letters
for ( 1 .. $loop )
{
$start = shift(@num) if ( ! $start );
$end = shift(@num);
my $next = $end;
$end--;
my $exclude = "FALSE";
open(FILE, "<", "$filename") || error("Cannot open file ($!)");
while (<FILE>)
{
my $line = $_;
$test = $. if ( eof );
if ( $. == $start && $line =~ /^P\|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\|1I\|IR\|/)
{
print OUTCANC "$line";
$exclude = "TRUECANC";
next;
}
if ( $. >= $start && $. <= $end && $exclude =~ "TRUECANC")
{
print OUTCANC "$line";
} elsif ( $. >= $start && $. <= $end && $exclude =~ "FALSE"){
print $_;
}
}
close FILE;
$end = ++$test if ( $end < $start );
$start = $next if ($next);
}
#Lets print the last letter in the file
my $exclude = "FALSE";
open(FILE, "<", "$filename") || error("Cannot open file ($!)");
while (<FILE>)
{
my $line = $_;
if ( $. == $start && $line =~ /^P\|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\|1I\|IR\|/)
{
$exclude = "TRUECANC";
next;
}
if ( $. >= $start && $. <= $end && $exclude =~ "TRUECANC")
{
print OUTCANC "$line";
} elsif ( $. >= $start && $. <= $end && $exclude =~ "FALSE"){
print $_;
}
}
close FILE;
close OUTCANC;
#----------------------------------------------------------------
sub message
{
my $m = shift or return;
print("$m\n");
}
sub error
{
my $e = shift || 'unknown error';
print("$0: $e\n");
exit 0;
}
答案 0 :(得分:2)
有些东西可以加快脚本速度,比如删除不必要的正则表达式使用。
/^P\|/
相当于"P|" eq substr $_, 0, 2
。$foo =~ "BAR"
可以是-1 != index $foo, "BAR"
。然后有一些重复的代码。将其分解为sub不会提高性能本身,但可以更容易推断脚本的行为。
有很多不必要的字符串,例如"$filename"
- $filename
就足够了。
但最糟糕的罪犯就是这样:
for ( 1 .. $loop ) {
...
open FILE, "<", $filename or ...
while (<FILE>) {
...
}
...
}
您只需要在一次中读取该文件,最好是在数组中读取。你可以循环索引:
for ( 1 .. $loop ) {
...
for my $i (0 .. $#file_contents) {
my $line = $file_contents[$i];
... # swap $. for $i, but avoid off-by-one error
}
...
}
磁盘IO 慢,因此请尽可能缓存!
我还看到您使用$exclude
变量作为布尔值,其值为FALSE
和TRUECANC
。为什么不0
和1
,所以您可以直接在条件中使用它?
您可以在if / elsif中分解常见测试:
if (FOO && BAR) { THING_A }
elsif (FOO && BAZ) { THING_B }
应该是
if (FOO) {
if (BAR) { THING_A }
elsif (BAZ) { THING_B }
}
$. == $start && $line =~ /^P\|.../
测试可能很愚蠢,因为$start
只包含以P|
开头的行数 - 所以这里的正则表达式就足够了。
如果我已正确理解了脚本,那么以下内容应该会显着提高性能:
#!/usr/bin/perl
use strict;
use warnings;
my ($filename, $cancfile) = @ARGV;
open my $fh, "<", $filename or die "$0: Couldn't open $filename: $!";
my (@num, @lines);
while (<$fh>)
{
push @lines, $_;
push @num, $#lines if "P|" eq substr $_, 0, 2;
}
open my $outcanc, ">>", $cancfile or die "$0: Couldn't open $cancfile: $!";
for my $i ( 0 .. $#num )
{
my $start = $num[$i];
my $end = ($num[$i+1] // @lines) - 1;
# pre v5.10:
# my $end = (defined $num[$i+1] ? $num[$i+1] : @lines) - 1
if ($lines[$start] =~ /^P[|][0-9]{9}[|]1I[|]IR[|]/) {
print {$outcanc} @lines[$start .. $end];
} else {
print STDOUT @lines[$start .. $end];
}
}
清理脚本。该文件缓存在一个数组中。只迭代实际需要的数组部分 - 我们从前面的 O(n·m)开始 O(n)。
对于你未来的脚本:证明循环和变异变量的行为并非不可能,但是单调乏味且烦人。意识到
for (1 .. @num) {
$start = shift @num unless $next; # aka "do this only in the first iteration"
$next = shift @num:
$end = $next - 1:
while (<FH>) {
...
$test = $. if eof
...
}
$end = ++test if $end < $start;
$start = $next if $next;
}
实际上是关于绕过第二个undef
中可能的shift
需要一些时间。我们可以在循环之后选择行号,而不是在内循环中测试eof
,因此我们不需要$test
。然后我们得到:
$start = shift @num;
for my $i (1 .. @num) {
$end = $num[$i] - 1:
while (<FH>) { ... }
$end = $. + 1 if $end < $start; # $end < $start only true if not defined $num[$i]
$start = $num[$i] if $num[$i];
}
将$i
向下翻译为1后,我们将界外问题仅限于一点:
for my $i (0 .. $#num) {
$start = $num[$i];
$end = $num[$i+1] - 1; # HERE: $end = -1 if $i == $#num
while (<FH>) { ... }
}
$end = $. + 1 if $end < $start;
用数组替换文件读取后(注意,数组索引和行号之间存在差异),我们看到如果我们将该迭代拉入{{{},则可以避免最终文件读取循环。 1}}循环,因为我们知道总共有多少行。所以说,我们做了
for
希望我的清理代码确实等同于原始代码。