PDL::P...hreads(3pm)

✨ PDL::P...hreads(3pm) ✨

okay so bestie, this is PDL::P...hreads(3pm) from the User Contributed Perl Documentation crowd β€” and yes, we are about to make threading actually make sense, because sharing PDL data between Perl threads is lowkey iconic when you do it right. πŸ’–


πŸ¦‹ NAME πŸ¦‹

PDL::Parallel::threads β€” sharing PDL data between Perl threads. that's it, that's the whole vibe. ✨


🌸 SYNOPSIS 🌸

bestie, look at this glow-up:

use PDL;
use PDL::Parallel::threads qw(retrieve_pdls share_pdls);

# Technically, this is pulled in for you by PDL::Parallel::threads,
# but using it in your code pulls in the named functions like async.
use threads;

# Also, technically, you can use PDL::Parallel::threads with
# single-threaded programs, and even with perl's not compiled
# with thread support.

# Create some shared PDL data
zeroes(1_000_000)->share_as('My::shared::data');

# Create an ndarray and share its data
my $test_data = sequence(100);
share_pdls(some_name => $test_data);  # allows multiple at a time
$test_data->share_as('some_name');    # or use the PDL method

# Kick off some processing in the background
async {
    my ($shallow_copy)
        = retrieve_pdls('some_name');

    # thread-local memory
    my $other_ndarray = sequence(20);

    # Modify the shared data:
    $shallow_copy++;
};

# ... do some other stuff ...

# Rejoin all threads
for my $thr (threads->list) {
    $thr->join;
}

use PDL::NiceSlice;
print "First ten elements of test_data are ",
    $test_data(0:9), "\n";

like, the whole lifecycle is right there: share it, grab it in a thread, mutate it, rejoin, print it. chef's kiss. πŸ’…


πŸ’œ DESCRIPTION πŸ’œ

so here's the tea: this module gives you a way to share PDL data between different Perl threads. and yes, there's a contrast to draw here. PDL's own posix thread support (peep PDL::ParallelCPU) is a whole different beast β€” this module lets you play with Perl's built-in threading model instead. And unlike Perl's threads::shared, this module is all about sharing data, not variables. data girlies, we're sharing data. 🫢

Because of that focus, it does not use attributes to mark shared variables. nah. instead, you have to explicitly share your data β€” either with the share_pdls function or with the share_as PDL method that this module introduces. both of those slap a name onto your data, and that name is what you use in other threads to grab the data back with retrieve_pdls. πŸ”₯

once your thread has access to the ndarray data, any modifications operate directly on the shared memory β€” which is literally the entire point of shared data, it's giving exactly what shared data is supposed to do. 😌

but listen, and listen good:

when you are completely done using a piece of data, you need to explicitly remove it from the shared pool with the free_pdls function. otherwise your data will keep on consuming memory until the originating thread terminates β€” or, put differently, you will have a memory leak. πŸ’€ not cute, don't do it.

now, this module lets you share two sorts of ndarray data:

  • data for an ndarray based on actual physical memory β€” think stuff like the result of zeroes in PDL::Core ✨
  • data using memory mapped files πŸ¦‹ (heads up: PDL v2.4.11 and higher support memory mapped ndarrays on all major platforms, including Windows)

and then there are other sorts of ndarrays whose data you cannot share, periodt. you can't directly share ndarrays that haven't been physicalised β€” though a simple make_physical in PDL::Core, sever in PDL::Core, or copy in PDL::Core will hand you an ndarray based on physical memory that you can share. πŸ’–

also, some functions wrap external data into ndarrays so you can poke at them with PDL methods β€” for example, see plmap in PDL::Graphics::PLplot and plmeridians in PDL::Graphics::PLplot. those you can't share directly either, but making a physical copy with copy in PDL::Core gives you something you can safely share. πŸŽ€

πŸ’Ύ Physical Memory πŸ’Ύ

the way this module pulls off data sharing of physical memory is remarkably cheap. like, cheaper than a simple affine transformation. cheaper!! 😭

here's the mechanic: for each call to retrieve_pdls, it creates a new shell of an ndarray and sets that ndarray's memory structure to point back to the same locations of the original (shared) ndarray. that's it. that's the magic trick. πŸ‘‘

which means you can share ndarrays created with standard constructors like zeroes in PDL::Core, pdl in PDL::Core, and sequence in PDL::Basic β€” or ndarrays that are the result of operations and function evaluations where there's no data flow, such as cat in PDL::Core (but not dog in PDL::Core 🐢), arithmetic, copy in PDL::Core, and sever in PDL::Core. when you're in doubt? just sever your ndarray before sharing and everything should work. ✨

important nuance alert!! the memory will always be freed when the originating thread terminates β€” even if it terminated cleanly. this can absolutely lead to segmentation faults when one thread exits and frees its memory before another thread has had a chance to finish its calculations on the shared data. 😭 bestie, protect yourself: use barrier synchronization to avoid this (via PDL::Parallel::threads::SIMD), or share data solely from your main thread. no cap.

πŸ—ΊοΈ Memory Mapped Data πŸ—ΊοΈ

As of 0.07, data sharing of memory-mapped ndarrays is exactly the same as any other kind. slay. πŸ’…

small caveat though: it has not been tested with PDL::IO::FlexRaw-mapped ndarrays. πŸ™ˆ

πŸŽ€ Package and Name Munging πŸŽ€

PDL::Parallel::threads lets you associate your data with a specific text name. put differently, it hands you a global namespace for data. 🌈

and if you've ever written C, you already know what that means: there's plenty of room for developers using this module to pick the same name for their data. without some combo of discipline and help, shared memory names would clash left and right. messy. 😭

one solution would be to make users (that's you, bestie) choose names that include their current package β€” like My-Module-workspace, or, following perlpragma, My::Module/workspace instead of just workspace. this is sometimes called name mangling.

well, the author decided this idea was so good that PDL::Parallel::threads does that second form of name mangling for you, automatically! πŸ’…βœ¨ of course, you can opt out if you wish. it's your world.

so here are the basic rules: the package name gets prepended to the name of the shared memory as long as the name is only composed of word characters β€” i.e. names matching /^\w+$/. here's an example showing exactly how this goes down:

package Some::Package;
use PDL;
use PDL::Parallel::threads 'retrieve_pdls';

# Stored under '??foo'
sequence(20)->share_as('??foo');

# Shared as 'Some::Package/foo'
zeroes(100)->share_as('foo');

sub do_something {
  # Retrieve 'Some::Package/foo'
  my $copy_of_foo = retrieve_pdls('foo');

  # Retrieve '??foo':
  my $copy_of_weird_foo = retrieve_pdls('??foo');

  # ...
}

# Move to a different package:
package Other::Package;
use PDL::Parallel::threads 'retrieve_pdls';

sub something_else {
  # Retrieve 'Some::Package/foo'
  my $copy_of_foo = retrieve_pdls('Some::Package/foo');

  # Retrieve '??foo':
  my $copy_of_weird_foo = retrieve_pdls('??foo');

  # ...
}

the upshot of all of this? if you use some module that also uses PDL::Parallel::threads, namespace clashes are highly unlikely to occur β€” as long as you (and the author of that other module) stick to simple names, like the sort of thing that works for variable names. easy. done. iconic. πŸ’–


and bestie, this page keeps going β€” part 2 is coming, so don't go anywhere. 🌸

πŸ”₯ FUNCTIONS πŸ”₯

okay so bestie, this module hands you three stand-alone functions and adds one brand-new PDL method. that's the whole roster. let's go. πŸ’–

share_pdls

Shares ndarray data across threads using the given names.

share_pdls (name => ndarray, name => ndarray, ...)

so it takes key/value pairs β€” the value is the ndarray you wanna store, and the key is the name you're storing it under. later you can pull that memory back with the retrieve_pdls method. ✨

sharing an ndarray that has physical memory (or that's memory-mapped) bumps up the data's reference count; you can knock it back down by calling free_pdls on the given name. ngl, in general this ends up doing exactly what you mean, and it only frees memory when you're really done using it.

my $data1 = zeroes(20);
my $data2 = ones(30);
share_pdls(foo => $data1, bar => $data2);

and you can pair this with constructors and fat commas to allocate a whole lil collection of shared memory for whatever algorithm you're cooking up:

share_pdls(
    main_data => zeroes(1000, 1000),
    workspace => zeroes(1000),
    reduction => zeroes(100),
);

share_pdls preserves the badflag and badvalue on ndarrays. πŸ’…

share_as

Method to share an ndarray's data across threads under the given name.

$pdl->share_as(name)

this PDL method lets you directly share an ndarray. it does the exact same thing as shared_pdls, but the way you invoke it is a lil different:

# Directly share some constructed memory
sequence(20)->share_as('baz');

# Share individual ndarrays:
my $data1 = zeroes(20);
my $data2 = ones(30);
$data1->share_as('foo');
$data2->share_as('bar');

like a bunch of other PDL methods, this one returns the just-shared ndarray. which means you can do some genuinely amusing things, like stashing partial calculations partway through a long chain:

my $results = $input->sumover->share_as('pre_offset') + $offset;

# Now you can get the result of the sumover operation
# before that offset was added, by calling:
my $pre_offset = retrieve_pdls('pre_offset');

this function gets you the same end result as share_pdls: There's More Than One Way To Do It, because it can make for easier-to-read code. in general I recommend using the share_as method when you only need to share a single ndarray memory space. 🫢

share_as preserves the badflag and badvalue on ndarrays.

retrieve_pdls

Obtain ndarrays providing access to the data shared under the given names.

my ($copy1, $copy2, ...) = retrieve_pdls (name, name, ...)

hand it a list of names, it hands you back a list of ndarrays that give you access to the data shared under those names. in scalar context it returns the ndarray corresponding with the first named data set β€” which is usually what you mean when you use a single name. but if you specify multiple names and then call it in scalar context? you get a warning telling you that you probably meant to say something differently. πŸ™ˆ

my $local_copy = retrieve_pdls('foo');
my @both_ndarrays = retrieve_pdls('foo', 'bar');
my ($foo, $bar) = retrieve_pdls('foo', 'bar');

retrieve_pdls preserves the badflag and badvalue on ndarrays.

free_pdls

Frees the shared memory (if any) associated with the named shared data.

free_pdls(name, name, ...)

this one marks the memory associated with the given names as no longer being shared, handling all the reference counting and other low-level stuff for you. you generally won't need to worry about the return value. but if you do care: you get a list of values β€” one for each name β€” where a successful removal gets the name and an unsuccessful removal gets an empty string.

so, if you say free_pdls('name1', 'name2') and both removals were successful, you'll get ('name1', 'name2') back. if there was trouble removing name1 (because there's no memory associated with that name), you'll get ('', 'name2') instead. which means you can handle trouble with perl greps and other conditionals:

my @to_remove = qw(name1 name2 name3 name4);
my @results = free_pdls(@to_remove);
if (not grep {$_ eq 'name2'} @results) {
    print "That's weird; did you remove name2 already?\n";
}
if (not $results[2]) {
    print "Couldn't remove name3 for some reason\n";
}

this function simply removes an ndarray's memory from the shared pool. it does not interact with bad values in any way. but then again, it doesn't interfere with or screw up bad values either. πŸ€·β™€οΈ balance.


πŸ’€ DIAGNOSTICS πŸ’€

the greatest hits of "what did you just do". every one of these is real, bestie.

  • share_pdls: expected key/value pairs β€” you called share_pdl with an odd number of arguments, which means you could not have supplied key/value pairs. double-check that every ndarray (or filename) you supply is preceded by its shared name. ✨
  • share_pdls: you already have data associated with '$name' β€” you tried to share some data under $name, but some data is already associated with that name. typo? πŸ‘€ you can avoid namespace clashes with other modules by using simple names and letting PDL::Parallel::threads mangle the name internally for you.
  • share_pdls: Could not share an ndarray under name '$name' because ...
    • "... the ndarray does not have any allocated memory." β€” you tried to share an ndarray that does not have any memory associated with it.
    • "... the ndarray's data does not come from the datasv." β€” you tried to share an ndarray that has a funny internal structure, in which the data does not point to the buffer portion of the datasv. I'm not sure how that could happen without triggering a more specific error, so I hope you know what's going on if you get this. :-) 🫠
  • share_pdls passed data under '$name' that it does not know how to store β€” share_pdls only knows how to store raw data ndarrays. it'll croak if you try to share other kinds of ndarrays, and it'll throw this error if you try to share anything else, like a hashref. no hashrefs allowed at the function. 🚫
  • retrieve_pdls: '$name' was created in a thread that has ended or is detached β€” in some other thread, you added some data to the shared pool. if that thread ended without you freeing that data (or the thread has become a detached thread), then we cannot know if the data is available. you should always free your data from the data pool when you're done with it, to avoid this error. post-cleanup glow is real.
  • retrieve_pdls could not find data associated with '$name' β€” pretty simple: either data has never been added under this name, or data under this name has been removed. πŸ’…
  • retrieve_pdls: requested many ndarrays... in scalar context? β€” this is just a warning, bestie. you requested multiple ndarrays (sent multiple names) but you called the function in scalar context. why do such a thing? 😭

🌸 LIMITATIONS 🌸

you cannot share memory mapped files that require features of PDL::IO::FlexRaw. that's a cool module that lets you pack multiple ndarrays into a single file, but simple cross-thread sharing is not trivial and is not (yet) supported.

if you're dealing with a physical ndarray, be a lil careful about how the memory gets freed.

if you don't call free_pdls on the data, it will persist in memory until the end of the originating thread β€” which means you have a classic memory leak. 😱 if another thread creates a thread-local copy of the data before the originating thread ends, but then tries to access the data after the originating thread ends, this will be fine, as the reference count of the datasv will have been increased. so like. it's giving "know your reference counts or perish".


πŸ¦‹ BUGS πŸ¦‹

None known at this point.

honestly? a slay. ✨


πŸ‘‘ SEE ALSO πŸ‘‘

PDL::ParallelCPU, MPI, PDL::Parallel::MPI, OpenCL, threads, threads::shared


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 distributed under the same terms as Perl itself.


⚠️ DISCLAIMER OF WARRANTY ⚠️

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.


and that's the whole page, bestie!! 🫢 this lil slice of documentation ships with perl v5.42.3 and it's dated 2026-08-20 β€” go forth, share your ndarrays across threads, and please free your data. mwah πŸ’‹

πŸ“¦ where this page comes from