The PDL command you want is indadd
. (Thanks to Chris Marshall, PDL Pumpking, for pointing that out to me elsewhere.)
PDL is designed for what I call "vectorized" operations. Compared with C operations, Perl operations are quite slow, so you want to keep the number of PDL method invocations to a minimum, and have each invocation do a lot of work. For example, this benchmark lets you specify the number of updates to perform in a single go (as a command-line parameter). The perl side has to loop, but the PDL side only performs five or so function calls:
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);
}
});
When I run this with an argument of 1, I get even worse performance than when using set
, which is what I expected:
$ perl script.pl 1
Rate pdl perl
pdl 21354/s -- -98%
perl 1061925/s 4873% --
That's a lot of ground to make up! But hold in there. If we do 100 iterations per round, we get an improvement:
$ perl script.pl 100
Rate pdl perl
pdl 16906/s -- -18%
perl 20577/s 22% --
And with 10,000 updates per round, PDL outperforms Perl by a factor of four:
$ perl script.pl 10000
Rate perl pdl
perl 221/s -- -75%
pdl 881/s 298% --
PDL continues to perform roughly 4x faster than plain Perl for even larger values.
Note that PDL's performance can degrade for more complex operations. This is because PDL will allocate and tear down large but temporary workspaces for intermediate operations. In that case, you may want to consider using Inline::Pdlpp
. However, that's not a beginner's tool, so don't jump there until you've determined it's really the best thing for you.
Another alternative to all of this is to use Inline::C
like so:
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]++;
}
}
For me, the Inline function consistently beats both Perl and PDL. For largish values of $updates_per_round
, say 1,000, I get Inline::C
's version roughly 5x faster than pure Perl and between 1.2x and 2x faster than PDL. Even when $updates_per_round
is just 1, where Perl beats PDL handily, the Inline code is 2.5x faster than the Perl code.
If this is all you need to accomplish, I recommend using Inline::C
.
But if you need to perform many manipulations to your data, you're best sticking with PDL for its power, flexibility, and performance. See below for how you can use vec()
with PDL data.