【发布时间】:2019-05-22 00:16:20
【问题描述】:
[在实施人们的建议后,我已更改以下代码以反映我当前正在运行的内容]
让我先声明一下,我不是程序员,只是一个使用 Perl 尽我所能完成某些文本处理工作的人。
我有一个生成频率列表的脚本。它基本上做了以下事情:
- 从格式为
$frequency \t $item的文件中读取行。任何给定的$item都可能出现多次,$frequency的值不同。 - 根据
$item的内容删除某些行。 - 对所有相同
$items 的频率求和,无论大小写如何,并将这些条目合并为一个。 - 对结果数组执行反向自然排序。
- 将结果打印到输出文件。
该脚本在最大约 1 GB 的输入文件上运行良好。但是,我需要处理高达 6 GB 的文件,但由于内存使用,这已被证明是不可能的。虽然我的机器有 32 GB 的 RAM,使用 zRam,并且为此目的在 SSD 上有 64 GB 的交换空间,但当内存使用量达到 70 GB 左右(92 GB)时,脚本将不可避免地被 Linux OOM 服务杀死总计)。
当然,真正的问题是我的脚本正在使用大量内存。我可以尝试添加更多交换,但我现在已经增加了两次,它就被吃光了。
所以我需要以某种方式优化脚本。这就是我在这里寻求帮助的原因。
下面是我现在正在运行的脚本的实际版本,保留了一些希望有用的 cmets。
如果您的 cmets 和建议包含足够的代码以实际允许我或多或少将其放入现有脚本中,我将非常感谢,因为我 没有 em> 一个程序员,正如我上面所说的,即使是像管道通过某个模块或另一个模块处理的文本这样看似简单的事情也会让我陷入严重的困境。
提前致谢!
(顺便说一句,我在 Ubuntu 16.04 LTS x64 上使用 Perl 5.22.1 x64。
#!/usr/bin/env perl
use strict;
use warnings;
use warnings qw(FATAL utf8);
use Getopt::Long qw(:config no_auto_abbrev);
# DEFINE VARIABLES
my $delimiter = "\t";
my $split_char = "\t";
my $input_file_name = "";
my $output_file_name = "";
my $in_basename = "";
my $frequency = 0;
my $item = "";
# READ COMMAND LINE OPTIONS
GetOptions (
"input|i=s" => \$input_file_name,
"output|o=s" => \$output_file_name,
);
# INSURE AN INPUT FILE IS SPECIFIED
if ( $input_file_name eq "" ) {
die
"\nERROR: You must provide the name of the file to be processed with the -i switch.\n";
}
# IF NO OUTPUT FILE NAME IS SPECIFIED, GENERATE ONE AUTOMATICALLY
if ( $output_file_name eq "" ) {
# STRIP EXTENSION FROM INPUT FILE NAME
$in_basename = $input_file_name;
$in_basename =~ s/(.+)\.(.+)/$1/;
# GENERATE OUTPUT FILE NAME FROM INPUT BASENAME
$output_file_name = "$in_basename.output.txt";
}
# READ INPUT FILE
open( INPUTFILE, '<:encoding(utf8)', $input_file_name )
or die "\nERROR: Can't open input file ($input_file_name): $!";
# PRINT INPUT AND OUTPUT FILE INFO TO TERMINAL
print STDOUT "\nInput file:\t$input_file_name";
print STDOUT "\nOutput file:\t$output_file_name";
print STDOUT "\n\n";
# PROCESS INPUT FILE LINE BY LINE
my %F;
while (<INPUTFILE>) {
chomp;
# PUT FREQUENCY IN $frequency AND THEN PUT ALL OTHER COLUMNS INTO $item
( $frequency, $item ) = split( /$split_char/, $_, 2 );
# Skip lines with empty or undefined content, or spaces only in $item
next if not defined $frequency or $frequency eq '' or not defined $item or $item =~ /^\s*$/;
# PROCESS INPUT LINES
$F{ lc($item) } += $frequency;
}
close INPUTFILE;
# OPEN OUTPUT FILE
open( OUTPUTFILE, '>:encoding(utf8)', "$output_file_name" )
|| die "\nERROR: The output file \($output_file_name\) couldn't be opened for writing!\n";
# PRINT OUT HASH WITHOUT SORTING
foreach my $item ( keys %F ) {
print OUTPUTFILE $F{$item}, "\t", $item, "\n";
}
close OUTPUTFILE;
exit;
以下是来自源文件的一些示例输入。它是制表符分隔的,第一列是$frequency,其余的一起是$item。
2 útil volver a valdivia
8 útil volver la vista
1 útil válvula de escape
1 útil vía de escape
2 útil vía fax y
1 útil y a cabalidad
43 útil y a el
17 útil y a la
1 útil y a los
21 útil y a quien
1 útil y a raíz
2 útil y a uno
【问题讨论】:
-
也许使用分而治之的方法?将大文件拆分成几个小文件,处理它们,汇总结果。
-
这不起作用,因为 $item 的给定值(例如,“apple”)可能在整个输入文件中出现 1000 次,每次都以不同的频率出现。而且我需要合并“苹果”(或其他)的所有实例并将它们的所有频率相加。将输入文件分成多个文件只会在每个拆分文件中实现这一点,而不是全局。
-
全局发生在合并和聚合结果时。如果不以某种方式拆分它,您将无法找到一种方法。
-
我实际上已经在预处理步骤中拆分了它,以清除无效条目。这将总文件大小减少了 3-5%,这仍然给我留下了大量文件,当我将它们合并回来时我无法处理这些文件。我相信,关键是我设法让 Perl 在处理 3-6 GB 的文件时使用 70+ GB 的内存。因此,我确信我在编程级别做错了什么。我的意思是,老实说——如果有 IgNobel 编码奖,我就赢了。
-
为什么在 while 循环中有
END { ... }块?这没有多大意义。您确定%F哈希是内存使用的罪魁祸首吗?使用Devel::Size 进行验证。
标签: perl