对存储在哈希中的AoAs进行操作。 PDL与无PDL

时间:2011-06-19 04:30:36

标签: perl pdl

我有一个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的简单脚本:

没有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);

与PDL

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);

4 个答案:

答案 0 :(得分:5)

PDL经过优化以处理数组计算。您正在为数据使用哈希值,但由于键是数字,因此可以根据PDL数组对象重新表示,以获得性能上的巨大成功。以下所有示例代码的PDL版本比原始没有PDL 代码运行 36X (并且 300X 比原始更快)使用PDL 代码)。

所有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 BookPDL 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);