perl文本挖掘代码无法处理大量数据

时间:2014-10-12 22:51:45

标签: performance perl

我正在做一个大型文本挖掘项目。我有100,000个文本文件。我一次从1,000个文档集中提取了两个和三个单词的短语,并创建了100个文件。每种文件都有大约800万行格式:

total_references num_docs_referencing_phrase phrase

我想通过处理100个中间文件来创建引用每个短语的总引用和文档数的聚合列表。为此,我写了这个程序。

#!/usr/bin/perl -w

$| = 1 ; # Don't buffer output

use File::Find ;

$dir = "/home/sl/phrase-counts" ;

find(\&processFile, $dir) ;

for $key ( keys %TOTALREFS ) {
    print "$TOTALREFS{$key} $NUMDOCS{$key} ${key}\n" ;
}

sub processFile {
   my $file = $_ ;
   my $fullName = $File::Find::name ;
   if ( $fullName =~ /\.txt$/ ) {
       $date = `date` ;
       chomp $date ;
       print "($date) file: $fullName\n" ;
       open INFILE, "$fullName" or die "Cannot read ${fullName}";
       while ( <INFILE> ) {
           my $line = $_ ;
           chomp $line ;
           ( $totalRefs, $numDocs, $phrase ) = split (/\s+/, $line, 3) ;
           $TOTALREFS{$phrase} += $totalRefs ;
           $NUMDOCS{$phrase} += $numDocs ;
       }
       close ( INFILE ) ;
    }
 }

在处理了8个左右的文件后,代码会产生奇怪的错误,然后它会挂起,即它会停止列出它应该处理的文件。

Use of uninitialized value $date in scalar chomp at ./getCounts line 21.
Use of uninitialized value $date in concatenation (.) or string at ./getCounts line 22.

我不相信这个问题确实是我的日期命令,特别是因为它对于处理的一些早期文件运行良好,并且因为每次运行它时运行中的同一点都不会出现问题。我认为问题是我的程序占用了太多的系统资源并破坏了运行环境的状态。运行顶部并观察内存使用高达97%的机器问题我虽然我注意到错误和挂起发生在顶部显示少量内存。并且,机器上有一些交换。

我的问题是,如何重写此程序以实际完成其执行?对于100个文件中的每个文件有800万行数据,可能有8亿行输出,但我猜想总数更有可能在5000万到1亿行之间。我已经对数据进行了一些清理,可以考虑更加积极地对短语进行消毒以减少数字,但我想了解如何更好地设计这些代码。

我见过的文章告诉程序员将他们的数据放入数据库。我担心的是更新数据库1亿次可能需要的时间。

建议?

3 个答案:

答案 0 :(得分:3)

看起来你正在运行* nix系统,所以让sort为你完成所有工作。它知道如何有效地使用内存。

sort -k 3 all_your_input_files*.txt > sorted.txt

为什么这样?因为现在对应于同一短语的所有行都出现在文件中的单个块中,所以您可以轻松地计算总数:只需编写一个简短的Perl脚本,将当前行的数字添加到当前总计中,并在短语更改时将其写出来从上一行(和结尾):

   my ($oldPhrase, $totTotalRefs, $totNumDocs) = (undef, 0, 0);
   while ( <INFILE> ) {
       my $line = $_ ;
       chomp $line ;
       ( $totalRefs, $numDocs, $phrase ) = split (/\s+/, $line, 3) ;
       if (defined($oldPhrase) && $phrase ne $oldPhrase) {
           print "$totTotalRefs $totNumDocs $oldPhrase\n" ;
           $totTotalRefs = $totNumDocs = 0;
       }

       $totTotalRefs += $totalRefs ;
       $totNumDocs += $numDocs ;
       $oldPhrase = $phrase;
   }
   close ( INFILE ) ;
   print "$totTotalRefs $totNumDocs $oldPhrase\n" ;

上面的代码未经测试,但我认为应该使用适当的样板文件。

[编辑:根据Sol的建议修复$oldPhrase永远不会设置的错误。“

答案 1 :(得分:2)

您将所有不同的短语存储为%TOTALREFS%NUMDOCS的键,因此事情至少是他们需要的两倍。

我建议您尝试以下

  • 添加use strictuse warnings(而不是-w)并正确声明所有变量

  • 不要在变量名中使用大写字母。大写字母保留用于全局标识符

  • 不要只是为了获得一天中的时间而启动100个子流程。只需像这样使用localtime

    printf "(%s) file: %s\n", scalar localtime, $full_name;
    
  • 仅使用find生成要处理的文件数组,所以它看起来像这样

    my @files;
    
    find(sub {
      push @files, $File::Find::name if -f and /\.txt$/i;
    }, $dir) ;
    

    然后,您可以使用简单的for循环

    处理每个文件
    for my $file (@files) {
      ...
    }
    
  • 两个传递给文件,第一次生成一个哈希,将每个短语与一个从零开始的整数相关联,第二个使用这些整数来索引数组{{1 }和@total_refs并增加其元素

你可能仍然没有记忆,但这些措施肯定会给你一个更好的机会。


<强>更新

为了清楚起见,这就是我想象它会起作用的方式。我已经将它作为一次通过完成,但最好像我描述的那样将其写为两遍,以便您可以检查中间数据。

请注意,除了确保编译之外,还没有进行测试。

@num_docs

答案 2 :(得分:2)

尝试使用比可用资源更多的资源会导致无法分配内存的异常或导致系统调用返回错误消息。它没有腐败记忆。

在这种情况下,反引号的结果是undef,这意味着命令无法执行。这很可能是因为你没有足够的内存。你在哪里知道无法执行程序是内存损坏的结果?!此外,您有一个您不理解的错误,但您没有检查返回的错误是什么?根据{{​​1}},反引号设置$?(以及$!$?时为system)。假设它是Perl中的一个错误是一个非常糟糕的假设,特别是当系统告诉你发生了什么错误时。

通过使用更合适和/或更有效的数据结构,或通过将部分数据保留在内存中(例如在磁盘上或数据库中)来减少内存使用。