работает на AoAs, хранящихся в хэше.PDL против нет PDL - PullRequest
3 голосов
/ 19 июня 2011

У меня есть хэш 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);

с 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 ]

5 голосов
/ 22 апреля 2012

PDL оптимизирован для обработки вычислений массива. Вы используете хеш для своих данных, но так как ключи являются числами, его можно переформулировать в терминах объектов массива PDL, чтобы получить большой выигрыш в производительности. Следующая вся версия примера кода PDL работает примерно на 36X быстрее, чем исходная без кода PDL 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 для вычислений. Домашняя страница PDL также является хорошей отправной точкой для всего PDL.

3 голосов
/ 19 июня 2011

Я несколько раз провел рефакторинг вашего кода, сначала переместив как можно больше сложности за пределы цикла. Во-вторых, я удалил слой абстракции или около того. Это значительно упростило выражение и сократило время выполнения примерно на 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() , Вы получаете больше информации из этого.

3 голосов
/ 19 июня 2011

Во-первых, 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;
}
0 голосов
/ 19 июня 2011

Исходя из предположения Немо, вот сценарий 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);
...