我有一个perl脚本我希望与之并行。
它由while
循环组成,在3400行的另一个while
循环内有超过11000行,这使得它非常慢。
open (FILE1, "File1.txt") or die "Can't open File1";
open (OUT, ">Outfile.txt");
while (<FILE1>)
{
my @data=split (/ /, $_);
my $RS=1;
open (FILE2, "File2.txt") or die "Can't open File2";
while (<FILE2>)
{
my @value=split (/ /, $_);
if ($data[$RS] == 1) {print OUT $value[1];$RS++;}
elsif ($data[$RS] == 2) {print OUT $value[2];$RS++;}
elsif ($data[$RS] == 0) {print OUT $value[3];$RS++;}
}
close FILE2;
}
我正在寻找一种与File1
的每一行相当的qsub的方法,这样我就可以发送3440个工作。有什么建议?如果可能的话,我想和perl呆在一起。我试图在bash脚本中插入此代码,但我真的不明白如何在另一个中插入一种语言。
我的File1
包含ID列表,其中包含列中的信息。然后,每列与File2
中的一行相关。我希望能够同时为多个ID运行第二个循环而不是一个接一个。
File1
ID RS_10 RS_15 RS_30
23 1 0 1
34 2 2 0
45 1 1 0
23 0 0 2
10 2 1 1
File2
RS_10 A B C
RS_15 D E F
RS_30 G H I
答案 0 :(得分:4)
优化的第一条规则是不要太早做(即在不分析代码的情况下跳到过早的结论)。
第二条规则可能是指缓存。
你的File2
不是很大。我会说我们把它加载到内存中。这具有以下优点:
关于第一点:你将每一行分成三千多次。可以更好地花费这些周期。
关于第三点:您似乎进行了索引转换:
1 → 1, 2 → 2, 0 → 3
我们可以使用一个执行此转换的数组(常量时间查找),而不是使用if / elsif-switch(线性复杂度)测试所有值:
my @conversion = (3, 1, 2);
...;
print OUT $value[$conversion[$data[$RS++]]];
如果此索引转换是常量,我们可以在解析File2
时执行一次且仅执行一次。这看起来像
use strict; use warnings;
use autodie; # automatic error handling
my @file2;
{
open my $file2, "<", "File2.txt";
while (<$file2>) {
my (undef, @vals) = split;
# do the reordering. This is equivalent to @vals = @vals[2, 0, 1];
unshift @vals, pop @vals;
push @file2, \@vals;
}
}
现在我们可以继续迭代File1
。从File2
打印相应的条目现在看起来像
open my $file1, "<", "File1.txt";
<$file1>; # remove header
while (<$file1>) {
my ($id, @indices) = split;
print $id, map $file2[$_][$indices[$_]], 0 .. $#indices;
# but I guess you'd want some separator in between
# If so, set the $, variable
}
这个算法仍然是二次的(map
只是伪装的for
- 循环),但这应该有一个更好的常数因子。给出示例输入的上述代码的输出是
23 A F G
34 B E I
45 A D I
23 C F H
10 B D G
($, = " "; $\ = "\n"
)。
最后一步(循环遍历File1)可以并行化,但这不太可能有用:IO很慢,线程之间的通信很昂贵(IPC更是如此),输出会随机顺序。我们可以产生一堆工作者,并在队列中传递未解析的行:
use threads; # should be 1st module to be loaded
use Thread::Queue;
use constant NUM_THREADS => 4; # number of cores
# parse the File2 data here
my $queue = Thread::Queue->new;
my @threads = map threads->new(\&worker), 1 .. NUM_THREADS;
# enqueue data
$queue->enqueue($_) while <$file1>;
# end the queue
$queue->enqueue((undef) x NUM_THREADS); # $queue->end in never versions
# wait for threads to complete
$_->join for @threads;
sub worker {
while(defined(my $_ = $queue->dequeue)) {
my ($id, @indices) = split;
print $id, map $file2[$_][$indices[$_]], 0 .. $#indices;
}
}
请注意,这会将@file2
复制到所有主题中。有趣的事实:对于示例数据,此线程化解决方案大约需要4倍的时间。这主要是线程创建的开销,因此这对您的数据来说不是问题。
在任何情况下,个人资料您的代码都可以看到哪里可以最有效地进行优化。我推荐优秀的Devel::NYTProf
。例如。对于使用这种非常有限的数据运行的非线程测试,autodie
隐含的开销和朋友比实际处理花费的时间更多。对你而言,最昂贵的一行可能是
print $id, map $file2[$_][$indices[$_]], 0 .. $#indices;
但我们在Perl里面做的事情并不多。