PARALLELCPU(1)

๐Ÿ“ฆ version 2.78.0, in epel 9

โœจ PARALLELCPU โœจ

ok so this is the PARALLELCPU page from the Perl User Contributed Documentation section, and it's literally all about making PDL go brrr on multiple cores ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ bestie, let's get into it

๐ŸŒธ NAME ๐ŸŒธ

PDL::ParallelCPU โ€” parallel processor multi-threading support in PDL ๐Ÿ’…

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

okay so PDL can split up numerical processing between multiple parallel processor threads (aka pthreads) using the set_autopthread_targ and set_autopthread_size functions. the whole point? it can improve processing performance โ€” by greater than 2-4X in most cases, no cap ๐Ÿ˜ญ๐Ÿ”ฅ โ€” by taking advantage of multi-core and/or multi-processor machines.

and as of 2.059, "online_cpus" in PDL::Core is used to set the number of threads used if PDL_AUTOPTHREAD_TARG is not set ๐Ÿ‘‘

๐ŸŽ€ SYNOPSIS ๐ŸŽ€

use PDL;

# Set target of 4 parallel pthreads to create, with a lower limit of
#  5Meg elements for splitting processing into parallel pthreads.
set_autopthread_targ(4);
set_autopthread_size(5);

$x = zeroes(5000,5000); # Create 25Meg element array

$y = $x + 5; # Processing will be split up into multiple pthreads

# Get the actual number of pthreads for the last
#  processing operation.
$actualPthreads = get_autopthread_actual();

# Or compare these to see CPU usage (first one only 1 pthread, second one 10)
# in the PDL shell:
$x = ones(10,1000,10000); set_autopthread_targ(1); $y = sin($x)*cos($x); p get_autopthread_actual;
$x = ones(10,1000,10000); set_autopthread_targ(10); $y = sin($x)*cos($x); p get_autopthread_actual;

๐Ÿ’œ Terminology ๐Ÿ’œ

bestie, real talk โ€” to reduce the confusion that existed in PDL before 2.075, this document uses pthreading to refer to processor multi-threading, which is the use of multiple processor threads to split up numerical processing into parallel operations. that's the word, that's the vibe, we're sticking with it ๐Ÿซถ

โœจ Functions that control PDL pthreads โœจ

this is just a brief listing and description of the PDL pthreading functions โ€” for the detailed tea, see the PDL::Core docs ๐Ÿ‘€

  • set_autopthread_targ โ€” set the target number of processor-threads (pthreads) for multi-threaded processing. setting auto_pthread_targ to 0 means that no pthreading will occur, periodt.

See PDL::Core for details.

  • set_autopthread_size โ€” set the minimum size (in Meg-elements or 2**20 elements) of the largest PDL involved in a function where auto-pthreading will be performed. for small PDLs it probably isn't worth starting multiple pthreads, so this function is used to define a minimum threshold where auto-pthreading won't be attempted ๐Ÿ™…โ€โ™€๏ธ

See PDL::Core for details.

  • get_autopthread_actual โ€” get the actual number of pthreads executed for the last pdl processing function. yes, actual. the real one. the receipts ๐Ÿ“‹

See PDL::get_autopthread_actual for details.

๐ŸŒˆ Global Control of PDL pthreading using Environment Variables ๐ŸŒˆ

okay this part is highkey iconic โ€” PDL pthreading can be globally turned on without modifying existing code by setting environment variables PDL_AUTOPTHREAD_TARG and PDL_AUTOPTHREAD_SIZE before running a PDL script. these environment variables get checked when PDL starts up, and calls to set_autopthread_targ and set_autopthread_size functions are made with the environment variable's values โœจ

for example, if the environment var PDL_AUTOPTHREAD_TARG is set to 3, and PDL_AUTOPTHREAD_SIZE is set to 10, then any pdl script will run as if the following lines were at the top of the file:

set_autopthread_targ(3);
set_autopthread_size(10);

๐Ÿงš How It Works ๐Ÿงš

the auto-pthreading process works by analyzing broadcast array dimensions in PDL operations (those above the operation's "signature" dimensions) and splitting up processing according to those and the desired number of pthreads (i.e. the pthread target or pthread_targ). the offsets, increments, and dimension-sizes (in case the whole dimension does not divide neatly by the number of pthreads) that PDL uses to step thru the data in memory are modified for each pthread so each one sees a different set of data when performing processing ๐Ÿง โœจ

๐Ÿ’– Example ๐Ÿ’–

$x = sequence(20,4,3); # Small 3-D Array, size 20,4,3

# Setup auto-pthreading:
set_autopthread_targ(2); # Target of 2 pthreads
set_autopthread_size(0); # Zero so that the small PDLs in this example will be pthreaded

# This will be split up into 2 pthreads
$c = maximum($x);

for the above example, the maximum function has a signature of "(a(n); [o]c())", which means that the first dimension of $x (size 20) is a Core dimension of the maximum function. the other dimensions of $x (size 4,3) are broadcast dimensions (i.e. will be broadcasted-over in the maximum function) ๐Ÿ‘‘

the auto-pthreading algorithm examines the broadcasted dims of size (4,3) and picks the 4 dimension, since it is evenly divisible by the autopthread_targ of 2. the processing of the maximum function is then split into two pthreads on the size-4 dimension, with dim indexes 0,2 processed by one pthread and dim indexes 1,3 processed by the other pthread ๐ŸŽฏ


๐Ÿ’€ Limitations ๐Ÿ’€

๐Ÿง Must have POSIX Threads Enabled

auto-pthreading only works if your PDL installation was compiled with POSIX threads enabled. this is normally the case if you are running on Windows, Linux, MacOS X, or other unix variants ๐Ÿซถ

๐Ÿ™ˆ Non-Threadsafe Code

not all the libraries that PDL intefaces to are thread-safe, i.e. they aren't written to operate in a multi-threaded environment without crashing or causing side-effects ๐Ÿ˜ญ some examples in the PDL core is the fft function and the pnmout functions.

to operate properly with these types of functions, the PPCode flag NoPthread has been introduced to indicate a function as not being pthread-safe. See PDL::PP docs for details.

๐Ÿ“ Size of PDL Dimensions and pthread Target

as of PDL 2.058, the broadcasted dimension sizes do not need to divide exactly by the pthread target, although if one does, it will be used ๐Ÿ‘Œ

if no dimension is as large as the pthread target, the number of pthreads will be the size of the largest broadcasted dimension.

and ngl here's the clever bit โ€” in order to minimise idle CPUs on the last iteration at the end of the broadcasted dimension, the algorithm that picks the dimension to pthread on aims for the largest remainder in dividing the pthread target into the sizes of the broadcasted dimensions. for example, if a PDL has broadcasted dimension sizes of (9,6,2) and the auto_pthread_targ is 4, the algorithm will pick the 1-th (size 6), as that will leave a remainder of 2 (leaving 2 idle at the end) in preference to one with size 9, which would leave 3 idle ๐Ÿงฎโœจ

โญ Speed improvement might be less than you expect.

if you have an 8-core machine and call auto_pthread_targ with 8 to generate 8 parallel pthreads, you probably won't get a 8X improvement in speed, due to memory bandwidth issues ๐Ÿ˜ญ even though you have 8 separate CPUs crunching away on data, you will have (for most common machine architectures) common RAM that now becomes your bottleneck. for simple calculations (e.g simple additions) you can run into a performance limit at about 4 pthreads. for more CPU-bound calculations the limit will be higher ๐Ÿ’…


Copyright 2011 John Cerney. You can distribute and/or modify this document under the same terms as the current Perl license.

See: http://dev.perl.org/licenses/


and that's the whole page bestie โ€” brought to you by perl v5.32.1, dated 2022-04-11, filed under PARALLELCPU(1) ๐ŸŽ€ go forth and pthread responsibly ๐Ÿ’–

๐Ÿ“ฆ where this page comes from