如何在Perl中加速这个嵌套的foreach循环?

时间:2017-06-15 03:20:22

标签: perl

我有一个Perl脚本,用于比较加载到两个数组中的两组数据,并且我试图使比较更有效。目前代码如下:

foreach(@{FILE_DATA}) {

    if((++$file_current_linenum % 200) == 0) {
        $progress = int($file_current_linenum / $file_total_lines * 10000) / 100;
        logg("Processed $file_current_linenum file rows, $progress%, $mismatches mismatches.");
    }


    $file_current_line = $_;

    $match_found = 0;

    foreach(@{DB_DATA}) {

        $db_current_line = $_;

        if($file_current_line->{"channel"} == $db_current_line->{"channel"} ) {

            if ($file_current_line->{"checksum"} == $db_current_line->{"checksum"} &&
                $file_current_line->{"time"} > ($db_current_line->{"date_part"} - $TIME_MATCH_TOLERANCE) && 
                $file_current_line->{"time"} < ($db_current_line->{"date_part"} + $TIME_MATCH_TOLERANCE) ){

                $match_found = 1;
                last; # break;
            }
        }
    }


    if($match_found != 1) {

        push(@results, $file_current_line);
        $mismatches++;

    }
}

我的第一个想法是从两个数组中删除匹配以减小池大小,这会影响迭代器的位置吗?

这两组数据最多可包含数万个元素,比较可能需要几个小时才能完成。

2 个答案:

答案 0 :(得分:3)

您的解决方案是O(DB * FILE)。

以下是O(DB + FILE)当且仅当从来没有多行具有相同的通道和校验和时:

my %DB_DATA;
for my $db_line (@DB_DATA) {
   push @{ $DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} } }, $db_line;
}

for my $file_line_idx (0..$#FILE_DATA) {
   my $file_line = $FILE_DATA[$file_line_idx];
   my $found = 0;
   if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
      if (my $r2 = $r1->{ $file_line->{checksum} } ) {
         my $file_time = $file_line->{time};
         for my $db_line (@$r2) {
            my $db_time = $db_line->{date_part};
            if (abs($file_time - $db_time) < $TIME_MATCH_TOLERANCE) {
               $found = 1;
               last;
            }
         }
      }
   }

   push @mismatches, $file_line if !$found;

   if ((($file_line_idx+1) % 200) == 0) {
      logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
         $file_line_idx+1,
         int(($file_line_idx+1)/@FILE_DATA) * 100,
         0+@mismatches,
      ));
   }
}

以下是O(DB + FILE),即使有很多行具有相同的通道和校验和,但如果$TIME_MATCH_TOLERANCE很大则使用大量内存:

my %DB_DATA;
for my $db_line (@DB_DATA) {
   for my $db_time (
      $db_line->{date_part} - $TIME_MATCH_TOLERANCE + 1
        ..
      $db_line->{date_part} + $TIME_MATCH_TOLERANCE - 1
   ) {
      ++$DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} }{$db_time};
   }
}

for my $file_line_idx (0..$#FILE_DATA) {
   my $file_line = $FILE_DATA[$file_line_idx];
   my $found = 0;
   if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
      if (my $r2 = $r1->{ $file_line->{checksum} } ) {
         if ($r2->{ $file_line->{time} } {
            $found = 1;
            last;
         }
      }
   }

   push @mismatches, $file_line if !$found;

   if ((($file_line_idx+1) % 200) == 0) {
      logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
         $file_line_idx+1,
         int(($file_line_idx+1)/@FILE_DATA) * 100,
         0+@mismatches,
      ));
   }
}

注意:假设时间戳是整数。如果它们不是,请在将它们用作键之前将它们转换为整数。

以下是O((DB + FILE)log DB)[非常接近O(DB + FILE)],即使有很多行具有相同的通道和校验和,并且使用最小的内存:

sub binsearch(&\@) {
   my ($compare, $array) = @_;

   my $i = 0;
   my $j = $#$array;
   return 0 if $j == -1;

   while (1) {
      my $k = int(($i+$j)/2);
      for ($array->[$k]) {
         my $cmp = $compare->()
            or return 1;

         if ($cmp < 0) {
            $j = $k-1;
            return 0 if $i > $j;
         } else {
            $i = $k+1;
            return 0 if $i > $j;
         }
      }
   }
}

my %DB_DATA;
for my $db_line (@DB_DATA) {
   push @{ $DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} } }, $db_line;
}

for my $r1 (values(%DB_DATA)) {
   for my $r2 (values(%$r1)) {
      @$r2 = sort { $a->{date_part} <=> $b->{date_part} } @$r2;
   }
}

for my $file_line_idx (0..$#FILE_DATA) {
   my $file_line = $FILE_DATA[$file_line_idx];
   my $found = 0;
   if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
      if (my $r2 = $r1->{ $file_line->{checksum} } ) {
         my $file_time = $file_line->{time};
         my $min_db_time = $file_time - $TIME_MATCH_TOLERANCE;
         my $max_db_time = $file_time + $TIME_MATCH_TOLERANCE;
         if ( binsearch {
              $_->{date_part} >= $max_db_time ? -1
            : $_->{date_part} <= $min_db_time ? +1
            : 0
         } @$r2 ) {
            $found = 1;
            last;
         }
      }
   }

   push @mismatches, $file_line if !$found;

   if ((($file_line_idx+1) % 200) == 0) {
      logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
         $file_line_idx+1,
         int(($file_line_idx+1)/@FILE_DATA) * 100,
         0+@mismatches,
      ));
   }
}

答案 1 :(得分:1)

你可以通过使用&#34; channel&#34;的串联从DB_DATA预先构建哈希来显着减少时间。和&#34;校验和&#34;值作为键,每个值是具有该通道和校验和的所有DB_DATA条目的列表。这样,对于每个FILE_DATA条目,您只需要检查该列表。

如果有很多条目具有给定的通道和校验和,您可以尝试通过按date_part对它们进行排序,然后尝试二进制搜索以查找有效条目来进一步提高。

如果具有给定通道和校验和的条目非常少,则应将运行时间减少一百万左右,因为它将运行时间从O($#FILE_DATA * $#DB_DATA)减少到O ($#FILE_DATA + $#DB_DATA)。