3

我有一个 AoA 的哈希值:

$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

4 回答 4

5

PDL 针对处理数组计算进行了优化。您正在为数据使用散列,但由于键是数字,因此可以根据 PDL 数组对象对其进行重新表述,以获得性能上的巨大胜利。以下示例代码的所有 PDL 版本的运行速度比没有 PDL代码的原始版本快大约 36 倍(并且带有PDL代码的原始版本快 300 倍)。

所有 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 书。PDL 主页也是所有 PDL 事物的良好起点。

于 2012-04-22T15:45:08.967 回答
3

首先,除非数组很大,否则 PDL 不会有太大帮助。因此,不是使用索引为 0 到 10000 的散列,每个都有(基本上)七个标量元素,您可以改为创建七个 PDL 向量,每个向量包含 10001 个元素并使用向量操作对那些进行操作吗?

其次,$hash{$_}每次命名时都会对表达式进行评估,因此您应该将其排除在外。例如,在您的标准 Perl 代码中,您应该这样做:

my $vec = $hash{$_};
foreach my $i (0 .. 2){
    $error += (($vec->[0][$i]-$vec->[2][$i])*$vec->[1])**2;
}
于 2011-06-19T04:48:42.370 回答
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()cmpthese()函数。你可以从中获得更多信息。

于 2011-06-19T08:31:53.347 回答
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);
于 2011-06-19T17:43:17.843 回答