执行Perl脚本时解决内存不足错误

3

我正在尝试基于英语维基百科转储中发现的前100K个单词构建一个n-gram语言模型。我已经使用Java编写的修改后的XML解析器提取了纯文本,但需要将其转换为词汇表文件。

为此,我找到了一个据说可以完成此任务的perl脚本,但缺乏关于如何执行的说明。不用说,我是完全新手,第一次遇到需要使用Perl的情况。

当我运行此脚本时,在两台分别配备4GB RAM和运行Ubuntu 10.04和10.10的双核机器上使用7.2GB文本文件时,我遇到了内存不足错误。

当我联系作者时,他说这个脚本在配备4GB RAM的MacBook Pro上运行良好,并且在使用perl 5.12对6.6GB文本文件执行时,总内存使用量约为78 MB。作者还表示,该脚本逐行读取输入文件并在内存中创建哈希表。

脚本如下:

#! /usr/bin/perl

use FindBin;
use lib "$FindBin::Bin";

use strict;
require 'english-utils.pl';

## Create a list of words and their frequencies from an input corpus document
## (format: plain text, words separated by spaces, no sentence separators)

## TODO should words with hyphens be expanded? (e.g. three-dimensional)

my %dict;
my $min_len = 3;
my $min_freq = 1;

while (<>) {

    chomp($_);
    my @words = split(" ", $_);

    foreach my $word (@words) {

        # Check validity against regexp and acceptable use of apostrophe

        if ((length($word) >= $min_len) && ($word =~ /^[A-Z][A-Z\'-]+$/) 
        && (index($word,"'") < 0 || allow_apostrophe($word))) {
            $dict{$word}++;
        }
    }

}

# Output words which occur with the $min_freq or more often

foreach my $dictword (keys %dict) {
    if ( $dict{$dictword} >= $min_freq ) {
        print $dictword . "\t" . $dict{$dictword} . "\n";
    }
}

我正在通过命令行执行此脚本:mkvocab.pl corpus.txt

附加的额外脚本只是一个正则表达式脚本,用于测试撇号的位置以及它们是否符合英语语法规则。

我认为内存泄漏是由于不同的版本导致的,因为5.10已安装在我的计算机上。所以我升级到了5.14,但错误仍然存在。根据free -m,我系统上有大约1.5GB的空闲内存。

由于我完全不熟悉语言的语法和结构,您能指出问题区域以及问题存在的原因和如何修复它吗?


你的输入文件中有很长的行吗?如果你的输入文件没有换行符,那么你将会在内存中保存大量数据。即使假设你的单词有一些重复,你的哈希表也可能会非常大。 - TLP
2个回答

7

如果单词有些重复,例如“the”出现了17,000次等,则将一个7.2Gb的文件加载到哈希表中是可能的。但这似乎还是很多。

您的脚本假设文件中的行已适当地分隔。如果文件不包含换行符,则会将整个文件加载到内存中的$_,然后使用split再加入大量数据到哈希表中。这将给任何系统带来压力。

一个想法是使用空格" "作为输入记录分隔符。它将执行与您已经使用split相同的操作,但它将保留其他空白字符,并且不会像split那样漂亮地修剪多余的空格。例如:

$/ = " ";
while (<>) {
    for my $word ( split ) {  # avoid e.g. "foo\nbar" being considered one word
        if (
              (length($word) >= $min_len) &&
              ($word =~ /^[A-Z][A-Z\'-]+$/) &&
              (index($word,"'") < 0 || allow_apostrophe($word))
        ) {
            $dict{$word}++;
        }
    }
}

这将允许即使是非常长的行也可以被分割成易于阅读的小块,前提是单词之间有空格(而不是制表符或换行符)。


问题已经解决。在一台双核笔记本电脑上,配备7200转硬盘,执行时间约为1小时,没有出现内存问题。谢谢! - Jason

3
尝试运行。
dos2unix corpus.txt

有可能你正在将整个文件作为一行来阅读...


这可能会导致出现格式混乱或错误。为了避免这种情况,请确保在适当的位置使用换行符。

这可能是一种可能性,因为输出文本仅由编辑器的尺寸包裹... - Jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接