您想要的 PDL 命令是indadd
. (感谢 PDL Pumpking 的 Chris Marshall 在其他地方向我指出这一点。)
PDL 专为我所说的“矢量化”操作而设计。与 C 操作相比,Perl 操作相当慢,因此您希望将 PDL 方法调用的次数保持在最低限度,并且每次调用都做大量工作。例如,此基准测试允许您指定一次执行的更新次数(作为命令行参数)。perl 端必须循环,但 PDL 端只执行五个左右的函数调用:
use PDL;
use Benchmark qw/cmpthese/;
my $updates_per_round = shift || 1;
my $N = 1_000_000;
my @perl = (0 .. $N - 1);
my $pdl = zeroes $N;
cmpthese(-1,{
perl => sub{
$perl[int(rand($N))]++ for (1..$updates_per_round);
},
pdl => sub{
my $to_update = long(random($updates_per_round) * $N);
indadd(1,$to_update,$pdl);
}
});
当我使用参数 1 运行它时,我得到的性能甚至比使用 时更差set
,这是我所期望的:
$ perl script.pl 1
Rate pdl perl
pdl 21354/s -- -98%
perl 1061925/s 4873% --
这是一个很大的基础来弥补!但坚持在那里。如果我们每轮进行 100 次迭代,我们会得到改进:
$ perl script.pl 100
Rate pdl perl
pdl 16906/s -- -18%
perl 20577/s 22% --
每轮更新 10,000 次,PDL 的性能比 Perl 高出四倍:
$ perl script.pl 10000
Rate perl pdl
perl 221/s -- -75%
pdl 881/s 298% --
对于更大的值,PDL 的执行速度仍然比普通 Perl 快大约 4 倍。
请注意,对于更复杂的操作,PDL 的性能可能会降低。这是因为 PDL 将为中间操作分配和拆除大型但临时的工作空间。在这种情况下,您可能需要考虑使用Inline::Pdlpp
. 然而,这不是初学者的工具,所以在你确定它真的是最适合你的东西之前不要跳到那里。
所有这一切的另一种选择是Inline::C
像这样使用:
use PDL;
use Benchmark qw/cmpthese/;
my $updates_per_round = shift || 1;
my $N = 1_000_000;
my @perl = (0 .. $N - 1);
my $pdl = zeroes $N;
my $inline = pack "d*", @perl;
my $max_PDL_per_round = 5_000;
use Inline 'C';
cmpthese(-1,{
perl => sub{
$perl[int(rand($N))]++ for (1..$updates_per_round);
},
pdl => sub{
my $to_update = long(random($updates_per_round) * $N);
indadd(1,$to_update,$pdl);
},
inline => sub{
do_inline($inline, $updates_per_round, $N);
},
});
__END__
__C__
void do_inline(char * packed_data, int N_updates, int N_data) {
double * actual_data = (double *) packed_data;
int i;
for (i = 0; i < N_updates; i++) {
int index = rand() % N_data;
actual_data[index]++;
}
}
对我来说,内联函数始终胜过 Perl 和 PDL。对于较大的值$updates_per_round
,比如 1,000,我得到Inline::C
的版本比纯 Perl 快大约 5 倍,比 PDL 快 1.2 到 2 倍。即使$updates_per_round
只有 1,Perl 轻松击败 PDL,内联代码也比 Perl 代码快 2.5 倍。
如果这就是您需要完成的全部,我建议使用Inline::C
.
但是,如果您需要对数据执行许多操作,则最好坚持使用 PDL,因为它具有强大的功能、灵活性和性能。请参阅下文,了解如何使用vec()
PDL 数据。