Classification of Variables in parfor-Loops

Overview

MATLAB® Coder™ classifies variables inside a parfor-loop into one of the categories in the following table. It does not support variables that it cannot classify. If a parfor-loop contains variables that cannot be uniquely categorized or if a variable violates its category restrictions, the parfor-loop generates an error.

ClassificationDescription
LoopServes as a loop index for arrays
SlicedAn array whose segments are operated on by different iterations of the loop
BroadcastA variable defined before the loop whose value is used inside the loop, but not assigned inside the loop
ReductionAccumulates a value across iterations of the loop, regardless of iteration order
TemporaryA variable created inside the loop, but unlike sliced or reduction variables, not available outside the loop

Each of these variable classifications appears in this code fragment:

a=0;
c=pi;
z=0;
r=rand(1,10);
parfor i=1:10
    a=i;        % 'a' is a temporary variable
    z=z+i;      % 'z' is a reduction variable
    
    b(i)=r(i);  % 'b' is a sliced output variable; 
                % 'r' a sliced input variable
    
    if i<=c     % 'c' is a broadcast variable
	
      d=2*a;		% 'd' is a temporary variable
    end
end

Sliced Variables

A sliced variable is one whose value can be broken up into segments, or slices, which are then operated on separately by different threads. Each iteration of the loop works on a different slice of the array.

In the next example, a slice of A consists of a single element of that array:

parfor i = 1:length(A)
   B(i) = f(A(i));
end

Characteristics of a Sliced Variable

A variable in a parfor-loop is sliced if it has the following characteristics:

  • Type of First-Level Indexing — The first level of indexing is parentheses, ().

  • Fixed Index Listing — Within the first-level parenthesis, the list of indices is the same for all occurrences of a given variable.

  • Form of Indexing — Within the list of indices for the variable, exactly one index involves the loop variable.

  • Shape of Array — In assigning to a sliced variable, the right-hand side of the assignment is not [] or '' (these operators indicate deletion of elements).

Type of First-Level Indexing. For a sliced variable, the first level of indexing is enclosed in parentheses, (). For example, A(...). If you reference a variable using dot notation, A.x, the variable is not sliced.

Variable A on the left is not sliced; variable A on the right is sliced:

A.q(i,12)                        A(i,12).q

Fixed Index Listing. Within the first-level parentheses of a sliced variable's indexing, the list of indices is the same for all occurrences of a given variable.

Variable B on the left is not sliced because B is indexed by i and i+1 in different places. Variable B on the right is sliced.

parfor i = 1:10
  B(i) = B(i+1) + 1;
end
parfor i = 1:10
  B(i+1) = B(i+1) + 1;
end

Form of Indexing. Within the list of indices for a sliced variable, one index is of the form i, i+k, i-k, k+i, or k-i.

  • i is the loop variable.

  • k is a constant or a simple (nonindexed) variable.

  • Every other index is a constant, a simple variable, colon, or end.

When you use other variables along with the loop variable to index an array, you cannot set these variables inside the loop. These variables are constant over the execution of the entire parfor statement. You cannot combine the loop variable with itself to form an index expression.

In the following examples, i is the loop variable, j and k are nonindexed variables.

Variable A Is Not SlicedVariable A Is Sliced
A(i+f(k),j,:,3)
A(i,20:30,end)
A(i,:,s.field1)
A(i+k,j,:,3)
A(i,:,end)
A(i,:,k)

Shape of Array. A sliced variable must maintain a constant shape. In the following examples, the variable A is not sliced:

A(i,:) = [];
A(end + 1) = i;

Broadcast Variables

A broadcast variable is a variable other than the loop variable or a sliced variable that is not modified inside the loop.

Reduction Variables

A reduction variable accumulates a value that depends on all the iterations together, but is independent of the iteration order.

This example shows a parfor-loop that uses a scalar reduction assignment. It uses the reduction variable x to accumulate a sum across 10 iterations of the loop. The execution order of the iterations on the threads does not matter.

x = 0;
parfor i = 1:10
   x = x + i;
end
x

Where expr is a MATLAB expression, reduction variables appear on both sides of an assignment statement.

X = X + exprX = expr + X
X = X - exprSee Reduction Assignments, Associativity, and Commutativity of Reduction Functions
X = X .* exprX = expr .* X
X = X * exprX = expr * X
X = X & exprX = expr & X
X = X | exprX = expr | X
X = min(X, expr)X = min(expr, X)
X = max(X, expr)X = max(expr, X)
X=f(X, expr)
Function f must be a user-defined function.
X = f(expr, X)
See Reduction Assignments, Associativity, and Commutativity of Reduction Functions

Each of the allowed statements is referred to as a reduction assignment. A reduction variable can appear only in assignments of this type.

The following example shows a typical usage of a reduction variable X:

X = ...;            % Do some initialization of X
parfor i = 1:n
    X = X + d(i);
end

This loop is equivalent to the following, where each d(i) is calculated by a different iteration:

X = X + d(1) + ... + d(n)

If the loop were a regular for-loop, the variable X in each iteration would get its value either before entering the loop or from the previous iteration of the loop. However, this concept does not apply to parfor-loops.

In a parfor-loop, the value of X is not updated directly inside each thread. Rather, additions of d(i) are done in each thread, with i ranging over the subset of 1:n being performed on that thread. The software then accumulates the results into X.

Similarly, the reduction:

r=r<op> x(i)
is equivalent to:
r=r<op>x(1)] <op>x(2)...<op>x(n)
The operation <op> is first applied to x(1)...x(n), then the partial result is applied to r.

If operation <op> takes two inputs, it should meet one of the following criteria:

  • Take two arguments of typeof(x(i)) and return typeof(x(i))

  • Take one argument of typeof(r) and one of typeof(x(i)) and return typeof(r)

Rules for Reduction Variables

Use the same reduction function or operation in all reduction assignments.  For a reduction variable, you must use the same reduction function or operation in all reduction assignments for that variable. In the following example, the parfor-loop on the left is not valid because the reduction assignment uses + in one instance, and * in another.

Invalid Use of Reduction VariableValid Use of Reduction Variable
parfor i = 1:n
  if A > 5*k
    A = A + 1;
  else 
    A = A * 2;
  end
parfor i = 1:n 
  if A > 5*k
    A = A * 3;
  else 
    A = A * 2;
  end

Restrictions on reduction function parameter and return types.  A reduction r=r<op> x(i), should take arguments of typeof(x(i)) and return typeof(x(i)) or take arguments of typeof(r) and typeof(x(i)) and return typeof(r).

In the following example, in the invalid loop, r is a fixed-point type and 2 is not. To fix this issue, cast 2 to be the same type as r.

Invalid Use of Reduction VariableValid Use of Reduction Variable
function r = fiops(in)
r=fi(in,'WordLength',20,...
  'FractionLength',14,...
  'SumMode','SpecifyPrecision',...
  'SumWordLength',20,...
  'SumFractionLength',14,...
  'ProductMode', 'SpecifyPrecision',...
  'ProductWordLength',20,...
  'ProductFractionLength',14);
parfor i = 1:10
    r = r*2;
end
r=fi(in,'WordLength',20,...
  'FractionLength',14,... 
  'SumMode','SpecifyPrecision',... 
  'SumWordLength',20,...
  'SumFractionLength',14,... 
  'ProductMode','SpecifyPrecision',... 
  'ProductWordLength',20,...
  'ProductFractionLength',14); 
T = r.numerictype; 
F = r.fimath; 
parfor i = 1:10 
    r = r*fi(2,T,F);     
end

In the following example, the reduction function fcn is invalid because it does not handle the case when input u is fixed point. (The + and * operations are automatically polymorphic.) You must write a polymorphic version of fcn to handle the expected input types.

Invalid Use of Reduction VariableValid Use of Reduction Variable
function [y0, y1, y2] = pfuserfcn(u)
    y0 = 0;
    y1 = 1;
    [F, N] = fiprops();
    y2 = fi(1,N,F);
    parfor (i=1:numel(u),12)
        y0 = y0 + u(i);
        y1 = y1 * u(i);
        y2 = fcn(y2, u(i));
    end
end
 
function y = fcn(u, v)
  y = u * v;
end
function [y0, y1, y2] = pfuserfcn(u)
    y0 = 0;
    y1 = 1;
    [F, N] = fiprops();
    y2 = fi(1,N,F);
    parfor (i=1:numel(u),12)
        y0 = y0 + u(i);
        y1 = y1 * u(i);
        y2 = fcn(y2, u(i));
    end
end
% fcn handles inputs of type double 
% and fi
function y = fcn(u, v)
    if isa(u,'double')
        y = u * v;
    else
        [F, N] = fiprops();
        y = u * fi(v,N,F);
    end
end

function [F, N] = fiprops()
    N = numerictype(1,96,30);
    F = fimath('ProductMode',...
        'SpecifyPrecision',...
        'ProductWordLength',96);
end

Reduction Assignments, Associativity, and Commutativity of Reduction Functions

Reduction Assignments. MATLAB Coder does not allow reduction variables to be read anywhere in the parfor-loop except in reduction statements. In the following example, the call foo(r) after the reduction statement r=r+i causes the loop to be invalid.

function r = temp %#codegen
  r = 0;
  parfor i=1:10
    r = r + i;
    foo(r);
  end
end

Associativity in Reduction Assignments. If you use a user-defined function f in the definition of a reduction variable, to get deterministic behavior of parfor-loops, the reduction function f must be associative.

Note

If f is not associative, MATLAB Coder does not generate an error. You must write code that meets this recommendation.

To be associative, the function f must satisfy the following for all a, b, and c:

f(a,f(b,c)) = f(f(a,b),c)

Commutativity in Reduction Assignments. Some associative functions, including +, ., min, and max, are also commutative. That is, they satisfy the following for all a and b:

f(a,b) = f(b,a)

The function f of a reduction assignment must be commutative. If f is not commutative, different executions of the loop might result in different answers.

Unless f is a known noncommutative built-in, the software assumes that it is commutative.

Temporary Variables

A temporary variable is a variable that is the target of a direct, nonindexed assignment, but is not a reduction variable. In the following parfor-loop, a and d are temporary variables:

a = 0;
z = 0;
r = rand(1,10);
parfor i = 1:10
   a = i;          % Variable a is temporary
   z = z + i;
   if i <= 5
      d = 2*a;     % Variable d is temporary
   end
end

In contrast to the behavior of a for-loop, before each iteration of a parfor-loop, MATLAB Coder effectively clears temporary variables. Because the iterations must be independent, the values of temporary variables cannot be passed from one iteration of the loop to another. Therefore, temporary variables must be set inside the body of a parfor-loop, so that their values are defined separately for each iteration.

A temporary variable in the context of the parfor statement is different from a variable with the same name that exists outside the loop.

Uninitialized Temporaries

Because temporary variables are cleared at the beginning of every iteration, MATLAB Coder can detect certain cases in which an iteration through the loop uses the temporary variable before it is set in that iteration. In this case, MATLAB Coder issues a static error rather than a run-time error, because there is little point in allowing execution to proceed if a run-time error will occur. For example, suppose you write:

  b = true;
  parfor i = 1:n
     if b && some_condition(i)
        do_something(i);
        b = false;
     end
     ...
  end

This loop is acceptable as an ordinary for-loop, but as a parfor-loop, b is a temporary variable because it occurs directly as the target of an assignment inside the loop. Therefore, it is cleared at the start of each iteration, so its use in the condition of the if is uninitialized. (If you change parfor to for, the value of b assumes sequential execution of the loop, so that do_something(i) is executed for only the lower values of i until b is set false.)