我正在计算具有相同值的组中两个项目的概率值(与生日问题类似的情况http://en.wikipedia.org/wiki/Birthday_problem)。
要做到这一点,我有24组三个值。组中的每个项目将具有来自24组中的每一组中的3个值。
我需要做的计算是获得这些值的所有可能迭代的乘积平方和。
考虑到必然的迭代性,这种迭代显然是非常密集的。
来自SE的输入我现在已经:
#!perl;
use List::Util qw(reduce);
use Set::CrossProduct;
my @array = ( ## AoA containing values for caluculation, cut-down to allow benchmarking
# [0.33, 0.33, 0.33], x11 more in full set
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33],
[0.33, 0.33, 0.33]
);
$val = 0;
my $iterator = Set::CrossProduct->new(\@array);
while (my $tuple = $iterator->get) {
$freq = reduce { $a * $b } @$tuple;
$val += ($freq*$freq);
}
$toprint=sprintf("%.50e", $val);
print $toprint;
基于上面代码中13组子集的快速基准测试,我估计在我的PC上运行完整的24组需要大约45天。是否有关于如何改进性能的建议?我不是在寻找奇迹,我很乐意在一周内完成它......
我没有在Perl上投入情感,所以如果有显着的性能优势,可以尝试转换到另一种语言。
提前感谢任何建议。
编辑:添加了R标签,因为这可能是我能够实施解决方案的第二好标签。
答案 0 :(得分:3)
这类问题是我的一杯茶。以下是我的想法:
这里的关键目标是减少评估结果所需的时间。您需要执行3 ^ 24 = 282亿个评估,这是无法避免的。但是,有一些技巧可以用来解决问题的更轻松的工作(评论也提到其中一些):
解锁并行化的关键(如前所述)是将工作分成更小的部分。在这个问题的上下文中,元组需要分成更易于管理的块。
如果我有一个四核处理器,我可能想把元组分成四个篮子:
my ( @baskets, $iter );
push @{ $baskets[ $iter++ % 4 ] }, $_ for $iterator->combinations;
这种功能非常容易归入一个子目录:
sub segment {
my $num_segments = shift;
my ( @baskets, $iter );
push @{ $baskets[ $iter++ % $num_segments ] }, $_ for @_;
return @baskets;
}
my @jobs = segment( 4, $iterator->combinations );
这里的线程使用应该足够,因为每元组计算是轻量级的(有关如何在Perl中使用线程的更多信息,请参考perldoc perlthrtut
):
use threads; # imports threads module
sub work { # What each thread will run
my @tuples = @_;
my $sum;
for my $tuple ( @tuples ) {
my $freq = 1;
$freq *= $_ for @$tuple;
$sum += $freq * $freq;
}
return $sum;
}
my @threads = map threads->new( \&work, @$_ ), @jobs; # Create and launch threads
# with different tuple sets
my $grand_total;
$grand_total += $_->join for @threads; # Accumulate sub-totals
免责声明:随着离散概率数量的增加,此解决方案的有效性也会增加。要判断这个提案是否会真正缩短获得结果的时间并不容易。
假设2 d.p.,所有元组中只能有100个不同的值(我想这就是生日问题发挥作用的地方)。鉴于每个元组中有24个概率,我想两个元组产生相同频率的可能性很高(统计学家可以证实这个假设)。这可以用一个简单的例子来证明,其中我将概率的数量限制在3:
[ 0.33, 0.45, 0.22 ], # Tuple A
.
.
.
[ 0.45, 0.22, 0.33 ], # Tuple B
此处,元组A和B将返回$freq
的相同值。如果我们计算这个$freq
值出现的次数,可以简单地计算$freq
一次并将其乘以“重复”元组的数量(从而用一块石头杀死许多元组)。 / p>
这将涉及检测重复次数:
my %seen;
for my $tuple ( $iterator->combinations ) {
my @sorted = sort @$tuple;
my $tuple_as_string = "@sorted";
$seen{$tuple_as_string}{count}++;
next unless exists $seen{$tuple_as_string}{freq};
my $freq = 1;
$freq *= $_ for @$tuple;
$seen{$tuple_as_string}{freq} = $freq;
}
my $grand_total;
for my $unique ( keys %seen ) {
my $count = $seen{$unique}{count};
my $freq = $seen{$unique}{freq};
$grand_total += $count * $freq * $freq;
}
如果您希望将此想法与并行化相结合,我建议在继续并行化操作之前先识别“唯一”元组。