Работает на AoAs, хранящихся в хэше. PDL против нет 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
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 ответа
Во-первых, 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;
}
PDL оптимизирован для обработки массивов. Вы используете хеш для своих данных, но так как ключи являются числами, его можно переформулировать в терминах объектов массива PDL, что значительно увеличивает производительность. Следующая версия примера кода PDL работает примерно в 36 раз быстрее, чем оригинал без кода PDL (и в 300 раз быстрее оригинала с кодом 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.
Я несколько раз рефакторинг вашего кода, сначала переместив как можно больше сложности за пределы цикла. Во-вторых, я удалил слой абстракции или около того. Это значительно упростило выражение и сократило время выполнения примерно на 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()
функции. Вы получаете больше информации из этого.
Основываясь на предложении Немо, вот сценарий 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);