我有一个AoAs哈希:
$hash{$key} = [
[0.0,1.0,2.0],
10.0,
[1.5,9.5,5.5],
];
我需要紧缩如下:
$err += (($hash{$key}[0][$_]-$hash{key}[2][$_])*$hash{$key}[1])**2 foreach (0 .. 2);
计算两个数组之间的平方加权差。由于我的哈希很大,我希望PDL有助于加速计算,但它不是出于某种原因。我还是PDL的新手,所以我可能搞砸了。 PDL下面的脚本慢了大约10倍。描述:以下两个脚本是我试图简单地表示我的程序中的内容。我将一些参考值读入哈希值,然后我将观察值(在运行中拉入哈希值)与一些权重的那些值进行比较。在脚本中,我将引用数组,权重和观察数组设置为某个任意固定值,但在运行时不会出现这种情况。
这里有两个没有PDL和PDL的简单脚本:
use strict;
use warnings;
use Time::HiRes qw(time);
my $t1 = time;
my %hash;
my $error = 0;
foreach (0 .. 10000){
$hash{$_} = [
[0.000, 1.000, 2.0000],
10.0,
[1.5,9.5,5.5],
];
foreach my $i (0 .. 2){
$error += (($hash{$_}[0][$i]-$hash{$_}[2][$i])*$hash{$_}[1])**2;
}
}
my $t2 = time;
printf ( "total time: %10.4f error: %10.4f\n", $t2-$t1,$error);
use strict;
use warnings;
use PDL;
use Time::HiRes qw(time);
my $t1 = time;
my %hash;
my $error = 0;
foreach (0 .. 10000){
$hash{$_}[0] = pdl[0.000, 1.000, 2.0000];
$hash{$_}[1] = pdl[10.0];
$hash{$_}[2] = pdl[1.5,9.5,5.5];
my $e = ($hash{$_}[0]-$hash{$_}[2])*$hash{$_}[1];
$error += inner($e,$e);
}
my $t2 = time;
printf ( "total time: %10.4f error: %10.4f\n", $t2-$t1, $error);
答案 0 :(得分:5)
PDL经过优化以处理数组计算。您正在为数据使用哈希值,但由于键是数字,因此可以根据PDL数组对象重新表示,以获得性能上的巨大成功。以下所有示例代码的PDL版本比原始没有PDL 代码运行 36X (并且 300X 比原始更快)使用PDL 代码)。
use strict;
use warnings;
use PDL;
use Time::HiRes qw(time);
my $t1 = time;
my %hash;
my $error = 0;
my $pdl0 = zeros(3,10001); # create a [3,10001] pdl
$pdl0 .= pdl[0.000, 1.000, 2.0000];
my $pdl1 = zeros(1,10001); # create a [1,10001] pdl
$pdl1 .= pdl[10.0];
my $pdl2 = zeros(3,10001); # create a [3,10001] pdl
$pdl2 .= pdl[1.5,9.5,5.5];
my $e = ($pdl0 - $pdl2)*$pdl1;
$error = sum($e*$e);
my $t2 = time;
printf ( "total time: %10.4f error: %10.4f\n", $t2-$t1, $error);
有关使用PDL进行计算的深入介绍,请参阅PDL Book。 PDL homepage也是PDL所有事情的良好起点。
答案 1 :(得分:3)
首先,除非阵列很大,否则PDL不会有太大帮助。因此,不是使用索引为0到10000的哈希,而是使用(基本上)七个标量元素,而是可以创建七个每个10001个元素的PDL向量,并使用向量运算对它们进行操作?
其次,每次命名时都会对表达式$hash{$_}
进行评估,因此您应该将其考虑在内。例如,在标准的Perl代码中,您应该这样做:
my $vec = $hash{$_};
foreach my $i (0 .. 2){
$error += (($vec->[0][$i]-$vec->[2][$i])*$vec->[1])**2;
}
答案 2 :(得分:3)
我重复了你的代码几次,首先尽可能多地移动循环之外的复杂性。其次,我删除了一层左右的抽象。这大大简化了表达式,并在保持相同结果的同时将运行时间缩短了约60%。
use Modern::Perl;
use Time::HiRes qw(time);
my $t1 = time;
my $error = 0;
my @foo = ( 0.000, 1.000, 2.0000 );
my $bar = 10.0;
my @baz = ( 1.5, 9.5, 5.5 );
foreach ( 0 .. 10000 ) {
$error += ( ( $foo[$_] - $baz[$_] ) * $bar )**2 for 0 .. 2
}
my $t2 = time;
printf ( "total time: %10.4f error: %10.4f\n", $t2-$t1,$error);
这只是普通的老Perl;没有PDL。希望这对您的项目有所帮助。
顺便说一下,在计算一段代码运行所需的时间时,我碰巧更喜欢Benchmark模块及其timethis()
,timethese()
和{{ 1}}功能。您可以从中获得更多信息。
答案 3 :(得分:0)
根据Nemo的建议,这是一个PDL脚本,可以实现适度的速度提升。我仍然是PDL绿色,所以可能有更好的方法。我还将散列中的值添加到参考/权重和观察的循环中,以使OP更像大型程序中发生的情况,请参阅上面的“描述”。
use strict;
use warnings;
use PDL;
use PDL::NiceSlice;
use Time::HiRes qw(time);
my $t1 = time;
my %hash;
my $nvals=10000;
#construct hash of references and weights
foreach (0 .. $nvals){
$hash{$_} = [
[0.000, 1.000, 2.0000],
[10.0, 10.0, 10.0],
];
}
#record observations
foreach (0 .. $nvals){
$hash{$_}[2] = [1.5,9.5,5.5];
}
my $tset = time;
my @ref;
my @obs;
my @w;
foreach (0 .. $nvals){
my $mat = $hash{$_};
push @ref, @{$mat->[0]};
push @w, @{$mat->[1]};
push @obs, @{$mat->[2]};
}
my $ref = pdl[@ref];
my $obs = pdl[@obs];
my $w = pdl[@w];
my $diff = (($ref-$obs)*$w)**2;
my $error = sum($diff);
my $t2 = time;
printf ( "$nvals time setup: %10.4f crunch: %10.4f total: %10.4f error: %10.4f\n", $tset-$t1,$t2-$tset, $t2-$t1,$error);