Perl:尝试加速解析分隔文件

Zip*_*970 3 regex perl split

我有一个大的平面文本文件,其中的行包含名称/值对("varname = value").这些对由多字符分隔符分隔.因此,此文件中的单行可能如下所示:

var1=value1|^|var2=value2|^|var3=value3|^|var4=value4
Run Code Online (Sandbox Code Playgroud)

每行包含大约50个名称/值对.

我需要迭代这个文件的行(大约有100,000行)并将名称/值对存储在一个哈希中,这样

$field{'var1'} = value1
$field{'var2'} = value2
etc...
Run Code Online (Sandbox Code Playgroud)

我做的是这样的:

# $line holds a single line from the file

my @fields = split( /\Q|^|\E/, $line );
foreach my $field (@fields) {
  my ($name, $value) = split( /=/, $field );
  $hash{$name} = $value;
}
Run Code Online (Sandbox Code Playgroud)

对整个文件的每一行执行此操作需要(在我的PC上)大约2秒钟.这似乎不是很长时间,但我真的想加快这一点.

在这2秒中,第一次分割大约需要0.6秒,而foreach循环大约需要1.4秒.所以我以为我会摆脱foreach循环并把它全部放在一个分裂中:

%hash = split( /\Q|^|\E|=/, $line );
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,以这种方式解析整个文件需要更长的时间!我的问题并不是为什么这需要更长时间(虽然理解为什么会是一个很好的奖励),但我的问题是,是否还有其他(更快)的方法来完成工作.

提前致谢.

------编辑下面这行------

我发现改变了这个:

%hash = split( /\Q|^|\E|=/, $line );
Run Code Online (Sandbox Code Playgroud)

进入这个:

$line =~ s/\Q|^|\E/=/g;
%hash = split( /=/, $line );
Run Code Online (Sandbox Code Playgroud)

使它快三倍!以这种方式解析整个文件现在需要一秒钟......

------这条线下面的片段------

use strict;
use Time::HiRes qw( time );

my $line = "a=1|^|b=2|^|c=3|^|d=4|^|e=5|^|f=6|^|g=7|^|h=8|^|i=9|^|j=10|^|k=11|^|l=12|^|m=13|^|n=14|^|o=15|^|p=16|^|q=17|^|r=18|^|s=19|^|t=20|^|u=21|^|v=22|^|w=23|^|x=24|^|y=25|^|z=26|^|aa=27|^|ab=28|^|ac=29|^|ad=30|^|ae=31|^|af=32|^|ag=33|^|ah=34|^|ai=35|^|aj=36|^|ak=37|^|al=38|^|am=39|^|an=40|^|ao=41|^|ap=42|^|aq=43|^|ar=44|^|as=45|^|at=46|^|au=47|^|av=48|^|aw=49|^|ax=50";

ResetTimer();
my %hash;
for( my $i = 1; $i <= 100000; $i++ ) {
  my @fields = split( /\Q|^|\E/, $line );
  foreach my $field (@fields) {
    my ($name, $value) = split( /=/, $field );
    $hash{$name} = $value;
  }
}
print Elapsed() . "\n";

ResetTimer();
%hash = ();
for( my $i = 1; $i <= 100000; $i++ ) {
  %hash = split( /\Q|^|\E|=/, $line );
}
print Elapsed() . "\n";

ResetTimer();
%hash = ();
for( my $i = 1; $i<=100000; $i++ ) {
  $line =~ s/\Q|^|\E/=/g;
  %hash = split( /=/, $line );
}
print Elapsed() . "\n";

################################################################################################################################
BEGIN {
  my $startTime;
  sub ResetTimer {
    $startTime = time();
    return $startTime;
  }
  sub Elapsed {
    return time() - $startTime;
  }
}
Run Code Online (Sandbox Code Playgroud)

Sob*_*que 6

我无法轻易回答您的性能问题,因为我需要一个测试用例.但我这与正则表达式的处理方式有关.

你可以看到它正在做什么use re 'debug';,并打印正则表达式步骤.

但对于更广泛的问题 - 我可能只是用全局解决它(假设您的数据就像示例一样简单):

#!/usr/bin/env perl
use strict;
use warnings; 
use Data::Dumper;

while ( <DATA> ) { 
   my %row = m/(\w+)=(\w+)/g;
   print Dumper \%row;
}

__DATA__
var1=value1|^|var2=value2|^|var3=value3|^|var4=value4
Run Code Online (Sandbox Code Playgroud)

你可以使用lookahead/behind匹配分隔符,如果你有更复杂的东西,但因为它是每行一个正则表达式,你不太经常调用正则表达式引擎,而且可能会更快.(但是如果没有测试用例,我无法确定).

如果你的数据比较复杂的话,或许:

my %row = s/\Q|^|\E/\n/rg =~ m/(.*)=(.*)/g;
Run Code Online (Sandbox Code Playgroud)

这将"强制"将输入拆分为新行,然后匹配'anything'='nothing'.但除非您的值包括空格/管道/元数据,否则这可能是过度的.

编辑您要使用的测试用例Benchmark:

#!/usr/bin/env perl
use strict;
use warnings;
use Benchmark qw ( cmpthese );

my $line =
  "a=1|^|b=2|^|c=3|^|d=4|^|e=5|^|f=6|^|g=7|^|h=8|^|i=9|^|j=10|^|k=11|^|l=12|^|m=13|^|n=14|^|o=15|^|p=16|^|q=17|^|r=18|^|s=19|^|t=20|^|u=21|^|v=22|^|w=23|^|x=24|^|y=25|^|z=26|^|aa=27|^|ab=28|^|ac=29|^|ad=30|^|ae=31|^|af=32|^|ag=33|^|ah=34|^|ai=35|^|aj=36|^|ak=37|^|al=38|^|am=39|^|an=40|^|ao=41|^|ap=42|^|aq=43|^|ar=44|^|as=45|^|at=46|^|au=47|^|av=48|^|aw=49|^|ax=50";

sub double_split {
   my %hash;
   my @fields = split( /\Q|^|\E/, $line );
   foreach my $field (@fields) {
      my ( $name, $value ) = split( /=/, $field );
      $hash{$name} = $value;
   }
}

sub single_split {
   my %hash = split( /\Q|^|\E|=/, $line );
}

sub re_replace_then_split {
   $line =~ s/\Q|^|\E/=/g;
   my %hash = split( /=/, $line );
}

sub single_regex {
   my %hash = $line =~ m/(\w+)=(\w+)/g;
}

sub compound {
   my %hash = $line =~ s/\Q|^|\E/\n/rg =~ m/(.*)=(.*)/g;
}

cmpthese(
   1_000_000,
   {  "Double Split"                 => \&double_split,
      "single split with regex"      => \&single_split,
      "Replace then split"           => \&re_replace_then_split,
      "Single Regex"                 => \&single_regex,
      "regex to linefeed them match" => \&compound
   }
);
Run Code Online (Sandbox Code Playgroud)

看起来结果如下:

                                 Rate Double Split single split with regex Single Regex Replace then split regex to linefeed them match
Double Split                  18325/s           --                     -4%         -34%               -56%                         -97%
single split with regex       19050/s           4%                      --         -31%               -54%                         -97%
Single Regex                  27607/s          51%                     45%           --               -34%                         -96%
Replace then split            41733/s         128%                    119%          51%                 --                         -93%
regex to linefeed them match 641026/s        3398%                   3265%        2222%              1436%                           --
Run Code Online (Sandbox Code Playgroud)

......我对最后一点感到有些怀疑,因为那太快了.可能会缓存那里发生的结果.

但看着它,正在减慢你的速度是正则表达式的变化:

sub single_split_with_alt {
   my %hash = split( /\Q|^|\E|=/, $line );
}

sub single_split {
      my %hash = split( /[\|\^\=]+/, $line );
}
Run Code Online (Sandbox Code Playgroud)

(我知道后者可能不是你想要的,但这只是为了说明目的)

得到:

                Rate  alternation single split
alternation  19135/s           --         -37%
single split 30239/s          58%           --
Run Code Online (Sandbox Code Playgroud)

但确实存在这样的问题,因为你的限制因素是磁盘IO,而不是CPU.

  • 复合()实际上是最慢的,但在你的例子中出现最快,因为它是针对已经改变的$行运行的(它在re_replace_then_split()中更改). (2认同)