%% Compare Performance of parfor, parfeval, and spmd
% Using spmd can be slower or faster than using parfor-loops or parfeval, depending on the type of computation. Overhead affects the relative performance of parfor-loops, parfeval, and spmd.
% For a set of tasks, parfor and parfeval typically perform better than spmd under these conditions.
% The computational time taken per task is not deterministic.
% The computational time taken per task is not uniform.
% The data returned from each task is small.
% Use parfeval when:
% You want to run computations in the background.
% Each task is dependent on other tasks.
% In this example, you examine the speed at which matrix operations can be performed when using a parfor-loop, parfeval, and spmd.
% First, create a local parallel pool p.

p = parpool('local');

%% Compute Random Matrices
% Examine the speed at which random matrices can be generated by using a parfor-loop, parfeval, and spmd. Set the number of trials (n) and the matrix size (for an m-by-m matrix). Increasing the number of trials improves the statistics used in later analysis, but does not affect the calculation itself.

m = 1000;
n = 20;

% Then, use a parfor-loop to execute rand(m) once for each worker. Time each of the n trials.

parforTime = zeros(n,1);
for i = 1:n
    tic;
    mats = cell(1,p.NumWorkers);
    parfor N = 1:p.NumWorkers
      mats{N} = rand(m);
    end
    parforTime(i) = toc;
end

% Next, use parfeval to execute rand(m) once for each worker. Time each of the n trials.

parfevalTime = zeros(n,1);
for i = 1:n
    tic;
    f(1:p.NumWorkers) = parallel.FevalFuture;
    for N = 1:p.NumWorkers
      f(N) = parfeval(@rand,1,m);
    end
    mats = fetchOutputs(f, "UniformOutput", false)';
    parfevalTime(i) = toc;
    clear f
end

% Finally, use spmd to execute rand(m) once for each lab. For details on labs and how to execute commands on them with spmd, see Run Single Programs on Multiple Data Sets. Time each of the n trials.

spmdTime = zeros(n,1);
for i = 1:n
    tic;
    spmd
        e = rand(m);
    end
    eigenvals = {e{:}};
    spmdTime(i) = toc;
end

% Use rmoutliers to remove the outliers from each of the trials. Then, use boxplot to compare the times.

% Hide outliers
boxData = rmoutliers([parforTime parfevalTime spmdTime]);

% Plot data
boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','')
ylabel('Time (seconds)')
title('Make n random matrices (m by m)')

% Typically, spmd requires more overhead per evaluation than parfor or parfeval. Therefore, in this case, using a parfor-loop or parfeval is more efficient.
%
%% Compute Sum of Random Matrices
% Next, compute the sum of random matrices. You can do this by using a reduction variable with a parfor-loop, a sum after computations with parfeval, or gplus with spmd. Again, set the number of trials (n) and the matrix size (for an m-by-m matrix).

m = 1000;
n = 20;

% Then, use a parfor-loop to execute rand(m) once for each worker. Compute the sum with a reduction variable. Time each of the n trials.

parforTime = zeros(n,1);
for i = 1:n
    tic;
    result = 0;
    parfor N = 1:p.NumWorkers
      result = result + rand(m);
    end
    parforTime(i) = toc;
end

% Next, use parfeval to execute rand(m) once for each worker. Use fetchOutputs on all of the matrices, then use sum. Time each of the n trials.

parfevalTime = zeros(n,1);
for i = 1:n
    tic;
    f(1:p.NumWorkers) = parallel.FevalFuture;
    for N = 1:p.NumWorkers
      f(N) = parfeval(@rand,1,m);
    end
    result = sum(fetchOutputs(f));
    parfevalTime(i) = toc;
    clear f
end

% Finally, use spmd to execute rand(m) once for each lab. Use gplus to sum all of the matrices. To send the result only to the first lab, set the optional targetlab argument to 1. Time each of the n trials.

spmdTime = zeros(n,1);
for i = 1:n
    tic;
    spmd
        r = gplus(rand(m), 1);
    end
    result = r{1};
    spmdTime(i) = toc;
end

% Use rmoutliers to remove the outliers from each of the trials. Then, use boxplot to compare the times.

% Hide outliers
boxData = rmoutliers([parforTime parfevalTime spmdTime]);

% Plot data
boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','')
ylabel('Time (seconds)')
title('Sum of n random matrices (m by m)')

% For this calculation, spmd is significantly faster than a parfor-loop or parfeval. When you use reduction variables in a parfor-loop, you broadcast the result of each iteration of the parfor-loop to all of the workers. By contrast, spmd calls gplus only once to do a global reduction operation, requiring less overhead. As such, the overhead for the reduction part of the calculation is O(n^2) for spmd, and O(mn^2) for parfor.
% Copyright 2020 The MathWorks, Inc.