Parallel pool on function that uses persistent variables

18 views (last 30 days)
Obviously parallel computing cannot handle correctly persistent variable, as showed in this minima example.
It seems when runing in parallel, the persistent variable remains unset [] even if I have set it before.
delete(gcp('nocreate')); % delete the current pool if any
ppobj = parpool('local'); %create parallel pool, 'threads' show the same behaviour
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 2).
BigData = ones(1000);
FooOutNormalRun = Foo(1) % 1000000, OK
FooOutNormalRun = 1000000
% Run
% FooOutRunWithppj = Foo(1)
% under parallel pool
Future = parfeval(ppobj, @Foo, 1, 1);
[j, FooOutRunWithppj] = fetchNext(Future);
FooOutRunWithppj % returns 0, expected 1000000
FooOutRunWithppj = 0
FooOutNormalRun = Foo(1) % 1000000, OK
FooOutNormalRun = 1000000
function Data = Store(action, Data)
persistent PDATA
if action == 1
PDATA = Data; % Store
elseif action == 2
Data = PDATA; % Retrieve
function s = Foo(count) %#ok
BigData = Store(2); % Retrieve
s = sum(BigData,'all');
Is the limitation mentioned somewhere in the documentation?
And more importantly any workaround (I try to reduce data broadcast in parallel computing, since my BigData is readonly and I don't want it to be copies (broadcasted) to the process, the overhread slows down and requires memory, and in principe I would be able to avoid that.
  1 Comment
Bruno Luong
Bruno Luong on 6 Jun 2022
I also try to store data inside a handle class like this
classdef StorageManagement < handle
% Usages
% instantiate:
% s = StorageManagement(Data)
% ...
% Data = s.GetData()
properties (SetAccess = immutable, NonCopyable = true)
% Constructor
function obj = StorageManagement(Data)
if nargin >= 1
obj.myData = Data;
warning('StorageManagement instantiates with empty data');
obj.myData = [];
function Data = GetData(obj, varargin)
Data = obj.myData;
Then retrieve the data during parallel call with
BigData = myhandleobj.GetData(); % myhandleobj is instance of StorageManagement
That works but the data seems to be broacasted to process. So I don't save anything if I pass BigData directly.
If I implement storage using persistent inside the class, it fails just like persistent inside a standard function as in my original question.

Sign in to comment.

Accepted Answer

Walter Roberson
Walter Roberson on 6 Jun 2022
I am having difficulty finding this documented, but it is well known. parpool local workers operate in different processes, and parfor and parfeval only copy over variables that it can clearly determine need to be copied. A persistent variable hidden inside the parsed version of a function is not "obvious" for this purpose. parfor and kin essentially save() and load() variables (equivalent to doing so) and do not bother to save and load (non-anonymous) functions.
The usual workaround is to parpool.constant and parfevalOnAll a call to get the value saved in a local persistent variable.
As is the case for global, the functionality is still available, but each worker will start with the variables clear.
It is possible that for your purposes, that using parpool threads might work for you, as the threads can share memory, so broadcast variables become much less expensive.
Bruno Luong
Bruno Luong on 6 Jun 2022
@Walter Roberson thanks for the tip of parallel.pool.Constant, it works beautifully to save the bandwidth.

Sign in to comment.

More Answers (1)

Edric Ellis
Edric Ellis on 6 Jun 2022
As Walter points out, workers (either threads or processes) do not share persistent variable workspaces. I too cannot find this explicitly mentioned in our doc. There's a hint here, but the restriction is more general than just parfor.
Whether you use threads or processes, you still need to arrange for each worker to get access somehow to your BigData. Either the contents need to be copied to the workers, or each worker needs to load/create it for itself. Using parallel.pool.Constant can work with either option. Again, Walter points out that "copying" data to thread-based workers is much more efficient than for process-based workers - although it sounds like current limitations mean that they don't work for you in any case.
Edric Ellis
Edric Ellis on 6 Jun 2022
The constraint on parfor loops being "order independent" is not really possible to enforce, other than at a syntactic level for the loop body itself. Use of persistent is just one way that you could subvert that. I agree that in your example, if somehow the persistent value was brought back to the client after the loop, it could have any value between 1 and 100.

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by