我有一个遗留项目,它从STDIN中获取大量数据并在perl脚本中逐行处理。行顺序并不重要。 这需要很长时间,所以我想并行完成。
经过一些研究后,我发现Parallel::Loops
似乎合适但我无法使其正常工作,因为$_
为空。我的代码是:
#Initialize all vars etc
$pl->while ( sub { <STDIN> }, sub {
print $_ # but $_ is empty
}
从STDIN ir parallel读取的其他方式也很受欢迎。
<小时/> 的更新
在我收到所有帮助后,我可以管理一些工作代码,谢谢。我将做一个简短的摘要。澄清:
这是一种解析器,它有超过3000行的正则表达式和 自动生成的条件。
我用于测试的输入是带有POS标记的文本,此文件中有1071406行。
我的硬件是:SSD光盘,中端i5上一代和8GB RAM DDR4。
结论:
最后,我要使用@ikegami解决方案,我认为当数据量增加时会更好。我将要处理的行数调整为100.000,因为它的结果比10.000更好。这个差异是8秒的aprox。
下一个自然步骤是将所有内容写入文件而不是使用STDOUT,我希望这有助于缩短时间。
答案 0 :(得分:4)
$_
永远不会设置,因为您从未分配给$_
!
不要忘记
while (<STDIN>) { ... }
是
的缩写while (defined( $_ = <STDIN> )) { ... }
这意味着您希望使用以下内容:
$pl->while ( sub { defined( $_ = <STDIN> ) }, sub {
print $_;
}
那就是说,打击$_
是一个坏主意。它很可能被调用者中的for (...)
别名化为其他变量。
这意味着你应该使用以下内容:
my $line;
$pl->while ( sub { defined( $line = <STDIN> ) }, sub {
print $line;
}
您可能会发现将工作分解为更粗糙的单位,这些线条会产生更好的性能,因为它会减少无意中听到的工作比率。
use constant WORK_UNIT_SIZE => 100;
my $done = 0;
my @lines;
$pl->while ( sub {
@lines = ();
return 0 if $done;
while (@lines < WORK_UNIT_SIZE) {
my $line = <>;
if (!defined($line)) {
$done = 1;
return 0+@lines;
}
push @lines, $line;
}
return 1;
}, sub {
for (@lines) {
print $_;
}
}
最后,您应该重复使用它们,而不是为每个工作单元创建新任务!以下使用线程演示了这一点。
use threads qw( async );
use Thread::Queue 3.01 qw( );
use constant NUM_WORKERS => 8;
use constant WORK_UNIT_SIZE => 100;
sub worker {
my ($job) = @_;
for (@$job) {
print $_;
}
}
my $q = Thread::Queue->new();
$q->limit(NUM_WORKERS * 4);
async { while (defined( my $job = $q->dequeue() )) { worker($job); } }
for 1..NUM_WORKERS;
my $done = 0;
while (!$done) {
my @lines;
while (@lines < WORK_UNIT_SIZE) {
my $line = <>;
if (!defined($line)) {
$done = 1;
last;
}
push @lines, $line;
}
$q->enqueue(\@lines) if @lines;
}
$q->end();
$_->join for threads->list;
答案 1 :(得分:1)
我不知道使用Parallel::Loops(可能有)的具体好处。 Parallel::ForkManager与Parallel::Loops
使用的内容相同。
use warnings;
use strict;
use feature 'say';
use Parallel::ForkManager;
my $max_procs = 30;
my $pm = Parallel::ForkManager->new($max_procs);
# Retrieve data returned by children in the callback
my %ret_data;
$pm->run_on_finish( sub {
my ($pid, $exit, $ident, $signal, $core, $dataref) = @_;
$ret_data{$pid} = $dataref;
});
while (my $input = <STDIN>)
{
chomp($input);
$pm->start and next;
my $ret = run_job($input);
$pm->finish(0, \$ret);
}
$pm->wait_all_children;
foreach my $pid (keys %ret_data) {
say "$pid returned: ${$ret_data{$pid}}";
}
sub run_job {
my ($input) = @_;
# your processing
return $input; # to have something to check
}
此代码返回子进程的标量,即单个值。您可以返回任何数据结构,请参阅文档中的 Retrieving data structures from child processes和this post作为示例。
数据通过文件返回,这可能会减慢大数据或许多快速流程的速度。
如果在终端上进行测试,请使用Ctrl-d
进行测试(或在last if $input !~ /\S/;
后添加chomp
以使用空行停止 - 但不要将数据传递给STDIN
其他方式)。
明确指出,每个STDIN
读取只需要处理一行。然后我们应该在产生一个新进程之前收集更多的行,否则会有太多的开销。
my $num_lines_to_collect = 1000;
my @lines_to_process; # collect lines for each fork
while (my $input = <STDIN>)
{
chomp($input);
push @lines_to_process, $input;
next if $. % $num_lines_to_collect != 0;
$pm->start and next;
my $ret = run_job( \@lines_to_process );
$pm->finish(0, \$ret);
@lines_to_process = (); # empty it for the next round
}
$pm->wait_all_children;
我们向数组@lines_to_process
添加行,并且仅当当前行号$.
是$num_lines_to_collect
的倍数时才继续使用新的分叉。因此,每个$num_lines_collect
都会启动一项工作,因此每项工作都会处理得那么多。我将其设置为1000
,即实验。
答案 2 :(得分:0)
这里最简单的方法可能是创建一个线程池,每个线程监听同一个队列,然后让一个线程(可能是主线程)读取文件并将每一行推送到队列。
use strict;
use warnings;
use Thread qw(async);
use Thread::Queue;
my $q = Thread::Queue->new();
$q->limit(32); # no point in reading in more than this into memory.
my @thr = map {
async {
while (defined (my $line = $q->dequeue()) ) {
print $line;
}
};
} 1..4; # 4 worker threads
while (<STDIN>)
{
$q->enqueue($_);
}
$q->end();
$_->join for Thread->list;
正如警告一样,请注意是否需要将数据从工作线程推送回主线程。它并不像其他语言那样微不足道。
更新:从线程切换到线程。虽然异步函数被记录为返回线程对象,但这对我来说似乎不起作用,因此也必须更改连接。