PDL::P...::SIMD(3pm)
okay bestie, come sit with me because we are about to get into PDL::Parallel::threads::SIMD(3pm) β that's the User Contributed Perl Documentation page for it β and honestly? it's giving parallel computing for the people. π
β¨ NAME β¨
PDL::Parallel::threads::SIMD β launch and synchronize Single-Instruction-Multiple-Dataset code π¦
πΈ SYNOPSIS πΈ
use PDL::Parallel::threads::SIMD qw(parallelize parallel_sync parallel_id);
# Launch five threads that all print a statement
parallelize {
my $pid = parallel_id;
print "Hello from parallel thread $pid\n";
} 5;
my @positions :shared;
# Run 47 time steps, performing the calculations
# for the time steps in parallel
my $size = 100;
my $N_threads = 10;
my $stride = $size / $N_threads;
parallelize {
my $pid = parallel_id;
# Set this thread's portion of the positions to zero
my $start = $stride * $pid;
my $end = $start + $stride - 1;
@positions[$start..$end] = (0) x $stride;
for (1..47) {
# First make sure all the threads are lined up
parallel_sync;
# Now calculate the next positions
$positions[$_] += $velocities[$_] for $start .. $end;
}
} $N_threads;
π DESCRIPTION π
ngl, in my experience, parallel algorithms are basically always written in this one shape called single-instruction, multiple-dataset (SIMD). Like, the exact same code runs in a bunch of threads and the only thing that's different between them is the data they're touching. That's literally the whole vibe for MPI and CUDA, those two big high-performance parallel computing frameworks. π«Ά
So the goal of this module is: let you write single-machine SIMD code in Perl. Is it gonna be as performant as MPI, CUDA, or OpenCL? Bestie, no. π But with a little work, the hope is it gives you decent results, and at the very least it's a genuinely good pedagogical tool for understanding how parallel algorithms actually work. β¨
Okay so SIMD code needs three facilities:
- a fast mechanism for data sharing,
- a way to enforce barrier synchronization,
- and an indicator of which thread is which β usually a thread id.
This module hands you the second and third of those. Data sharing? That's already available, thanks to general Perl data sharing (not fast, but easy to share data, periodt) and PDL::Parallel::threads, which gives you a simple way to share PDL data across threads and is actually pretty fast. π₯
The main character of this module is the parallelize function β it lets you specify your SIMD code in a way that's simple and obvious. Inside that block you can grab the parallelized thread id: a block-specific number between 0 and one less than however many threads are executing in your parallelized block. You get it by calling parallel_id. And also inside the block you can enforce a barrier synchronization point with parallel_sync. π
For example, here's a complete working script showing off parallelize and parallel_id:
use PDL::Parallel::threads::SIMD qw(parallelize parallel_id);
parallelize {
my $pid = parallel_id;
print "Hello from parallel thread $pid\n"
} 10;
When that runs on the original author's machine, the output is:
Hello from parallel thread 1
Hello from parallel thread 2
Hello from parallel thread 3
Hello from parallel thread 4
Hello from parallel thread 5
Hello from parallel thread 6
Hello from parallel thread 7
Hello from parallel thread 8
Hello from parallel thread 0
Hello from parallel thread 9
Look closely at that, bestie. Between thread 8 and 9 comes... thread 0. π And that's not a glitch β parallel threads have no guarantee of ordering, and for longer parallelized blocks the eventual order of a printout like this is often basically random. Not me being chaotic, it's just how it is. π
So yeah β the block you hand to parallelize gets executed ten times, but inside each one the value from parallel_id is a unique integer between 0 and 9. Perl assigns its own unique id to every thread it runs, so saying parallel thread ids here is a deliberate distinction from Perl's thread id. Perl's thread id keeps incrementally increasing over the life of your program, going up with each thread you spawn. The parallel thread id, though? It always starts counting from zero for a given parallelized block. β
Now, why would you even want each thread to have its own number that's different from the Perl-assigned one? The reason is that having unique, sequential, normalized numbers makes it so easy to split the work between threads in a simple, predictable way. For example, in the code below, the slice bounds get calculated per-thread based on the parallel thread id:
use PDL;
use PDL::Parallel::threads;
use PDL::Parallel::threads:SIMD qw(parallelize parallel);
use PDL::NiceSlice;
# Load the data with 7 million elements into $to_sum...
# ...
# Share it.
$to_sum->share_as('to-sum');
# Also allocate some shared, temporary memory:
my $N_threads = 10;
zeroes($N_threads)->share_as('workspace');
my $stride = $to_sum->nelem / $N_threads;
# Parallelize the sum:
parallelize {
my $pid = parallel_id;
my ($to_sum, $temporary)
= retrieve_pdls('to-sum', 'workspace');
# Calculate the thread-specific slice bounds
my $start = $stride * $pid;
my $end = $start + $stride - 1;
$end = $to_sum->nelem - 1 if $end >= $to_sum->nelem;
# Perform this thread's sum
$temporary($pid)
.= $to_sum($start:$end)->sum;
});
# This code will not run until that launch has returned
# for all threads, so at this point we can assume the
# workspace memory has been filled.
my $final_sum = retrieve_pdls('workspace')->sum;
print "The sum is $final_sum\n";
And as that last comment says, the parallelize block will always finish executing before the next line of Perl in your script runs. In other words: every thread does a barrier synchronization right before handing control back to your code. π
"Why would anybody want to force code to wait at a barrier," you ask? Bestie. π
Nontrivial multi-threaded code has to be able to set locations in the code that all threads must reach before any thread goes forward. That's called barrier synchronization, and it matters a lot when your threaded code has multiple stages. A really important example of an algorithm that needs barrier synchronization points is a time-stepping simulation β because there you need to make sure every thread reaches the "end" of the time step before anyone moves on to the next one, since the results of one thread's time step ostensibly depend on the previous results from other threads. If the calculations for each thread on one step depend on all (or at least some) of the threads having finished a previous set of calculations, then you should use a barrier synchronization event by calling parallel_sync. β¨
Because of that synchronization that happens right before control returns to your code, you can picture your code's execution timeline like this:
PDL::Parallel::threads::SIMD Execution
======================================
main thread
|
|
|
V
th0 th1 th2 th3 th4 th5 ... th(N-1)
| | | | | | |
| | | | | | |
V V V V V V V
main thread
|
|
|
V
That's the opposite vibe of, say, CUDA, where a thread-launch returns immediately β so you can go do calculations on your CPU while you wait for the GPU to finish:
CUDA Execution
==============
main thread
|
|
|
| --> thread launch
| th0 th1 th2 th3 th4 th5 ... th(N-1)
| | | | | | | |
| | | | | | | |
| V V V V V V V
|
|
|
|
V
It also contrasts with MPI, where there's no main thread to speak of at all: everything runs in one thread or another, and any central coordination has to be specifically orchestrated through a thread you choose. π¦
IMPORTANT WARNING, FR FR: if any thread callsparallel_sync, then ALL threads must callparallel_sync. Otherwise the thread will hang until, possibly, the next call for barrier synchronization β and that could lead to VERY confusing apparent errors in logic. π
Like, for example:
...
parallelize {
my $pid = parallel_id;
# do some calculations
# Do some thread-specific work
if ($pid < 5) {
# Notice the *two* barriers set up here:
parallel_sync;
# Do something that requires synchronization
parallel_sync;
}
else {
# THIS PART IS NECESSARY TO PREVENT PROBLEMS
# Call parallel_sync the same number of times
# in this else block as in the if block
parallel_sync;
parallel_sync;
}
} 10;
As a general rule: avoid putting parallel_sync in conditional blocks like if statements. while loops are another possible problem β if the condition in the while loop (more specifically, the number of iterations through the while loop) depends on thread-specific aspects of the data. You can do it, of course, but you have to be very careful that all threads make the same number of calls at the same algorithmically-intended point in the execution. π
<!-- part 1 of 2, more coming, do not sign off -->
β¨ FUNCTIONS β¨
okay so this module is serving exactly three functions bestie, and they're a little trio that only makes sense together: one of them launches a block of code in parallel across a bunch of threads, and the other two are meant to be called from inside that block. Like, one syncs up the different threads running that block, and one tells you which thread you are. π«Ά
π« parallelize
Launches a block of code in parallel across a bunch of threads. the whole point β¨
parallelize BLOCK N_THREADS
This one is hungry β it needs two arguments: the block you want to execute, and the number of threads you want launched to go execute it. It returns nothing at all, it's giving silent but powerful π
This is literally the way you say "hey, run this code in parallel." No other way in. That's the homie.
π parallel_sync
Synchronizes all threads at the given barrier. it's the group-chat "wait for everyone before we move on" function, no cap π¦
Usage:
parallelize {
# ...
parallel_sync;
# ...
} $N_threads;
This function enforces barrier synchronization among all the threads in your parallelized block. Takes no arguments, returns nothing. Clean, minimal, iconic. Periodt.
The barrier synchronization is tightly coupled with the parallelize function β like, they're bonded fr fr. You can only call parallel_sync from the middle of a parallelize block. If you call parallel_sync from outside a parallelize block, you will get an error. π¨
The docs literally say this: "I need to include an example and exposition on when and why to synchronize..." β so yeah, the author knows, it's on the list, it's coming. π
π parallel_id
Gives the thread's parallel id. your little name tag for whichever thread you're on π·οΈβ¨
Usage:
parallelize {
# ...
my $pid = parallel_id;
# ...
} $N_threads;
From within the parallelize block, you obtain the current thread's parallel id with this simple function. It's giving main character energy but for threads π
. When called outside the scope of a parallelize block, the function simply croaks. πΈπ
πΈ DIAGNOSTICS πΈ
So the page says this module does not croak. It does, however, issue a handful of warnings. And then it lists them and one of them is definitely called an exception, and I'm just the messenger here bestie, I'm not touching that π
"Cannot call parallel_sync outside of a parallelized block"β you tried to issue a barrier synchronization (parallel_sync) outside the context of aparallelizeblock, but that's the only context where it makes sense. Like showing up to a party that isn't happening. π"Cannot get parallel_id outside of a parallelized block"β you'll get this warning when you ask for a parallel id from code that is not executing in a parallel block. Heads up: the resulting return value will be the undefined value. π"Cannot nest parallelized blocks (yet)"β this exception gets thrown when you have aparallelizeblock within anotherparallelizeblock. That's not presently allowed, though the author says they're open to any ideas for implementing it if you have any. :-) π"Must request a positive number of parallelized threads"β if you send something that's not a positive integer as the number of threads to launch on your parallelized block, you will get this error. So like. Always specify a positive integer number. That's the vibe check and you failed it. β
π¦ LIMITATIONS π¦
First off, the author says they are actually quite pleased with how this module has turned out π β like, genuinely obsessed β but there are certainly some limitations. For example, you cannot launch a parallel block from within another parallel block. You can still create and join threads, you just cannot do it with the parallelize function. So close! π«
And then: "I'm sure there are plenty of limitations, but it's hard for me to see what differentiates a design goal from a limitation. Feedback on this would be much appreciated." β honestly? Relatable. A philosophical king. π
π BUGS π
None known at this point. clean sheet, we love to see it β¨
π SEE ALSO π
- The basic module for Perl parallel computing is
threads. - Work on this module was originally inspired by work on
PDL::Parallel::threads, so you might want to check that out. π₯ - Modules related to scientific parallel computing include
PDL::ParallelCPU,Parallel::MPI,Parallel::MPI::Simple,PDL::Parallel::MPIandOpenCL. - Other modules provide alternative parallel computing frameworks. These may be less suitable for scientific computing, but will likely serve other purposes:
Parallel::Loops,Gearman,Parallel::ForkManager,forks,Thread::Pool.
π AUTHOR, COPYRIGHT, LICENSE π
This module was written by David Mertens. The documentation is copyright (C) David Mertens, 2012. The source code is copyright (C) Northwestern University, 2012. All rights reserved. π
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. slay, open source π
π¨ DISCLAIMER OF WARRANTY π¨
okay bestie, listen up, this part is serious and I'm keeping every single word because it matters:
Parallel computing is hard to get right, and it can be exacerbated by errors in the underlying software. Please do not use this software in anything that is mission-critical unless you have tested and verified it yourself. I cannot guarantee that it will perform perfectly under all loads. I hope this is useful and I wish you well in your usage thereof, but BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. π€
fr fr read that one. no skimming. π
and that's a wrap on the whole page, bestie!! π this transcription came to you courtesy of perl v5.42.3, dated 2026-08-20, and it's filed under PDL::P...::SIMD(3pm) β go forth and parallelize responsibly, mwah π«Άπβ¨
π¦ where this page comes from
- ubuntu stonking: pdl 1:2.106-1build1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms