Activity

Because the CAAPP is a SIMD grid of processors, each CAAPP processing element (PE) does exactly the same operation as every other PE. Suppose we have stored in some plane a value that we will use in some further computation. Let us assume that there is a valid range for these values and that we must change the values outside of the range to the minimum and maximum values allowed. We can diagram this as shown below.

The diagram shows the set of all PEs and the subsets containing the values of x outside of the range [0,10]. The normal programming method for placing the values in range would be to use an if statement as in

    if      (x <  0) x = 0;
    else if (x > 10) x = 10;
If x is a scalar integer, then the meaning is clear. For values of the scalar x outside of the range, the value of x is set to either 0 or 10 and other values in the range are left unchanged.

If x is a plane, does the if statement make sense? x is a plane that contains not just one value but possibly thousands of values? We learned in a previous section that the result of x < 0 is a plane of ones and zeros with a value for every element of the plane x. In the above diagram, the PEs in the small circle labeled x < 0 would have a value of 1 and all other PEs would have the value 0. The if statement will only process scalar values and not a grid of values.

The C++ compiler will generate an error for the above if statement.

We would like only those values of x less than 0 or greater than 10 to be changed. Let us consider the possibilities. We may want to test a condition that applies to all PEs. An example would be a global flag. We may want to test an independent condition at each PE. An example would be testing the values in a plane for a particular value.

We may want to apply some operation to all PEs. An example would be initializing a plane. We may want to apply an operation to some PEs and not to others. An example would be setting out-of-bounds values to an in-bound value. Thus, there are four possibilities for controlling the CAAPP PEs:

  1. Global condition / global operation
  2. Global condition / local operation
  3. Local condition / global operation
  4. Local condition / local operation
Let us consider the first case using the following code:
    void ftn(int reset, CharPlane &x, CharScalar init_value)
     {
      if (reset) x = init_value;
     }
In this example we test a global condition (reset) and set all values of x to init_value if the global condition is set.

The second case does not really exist. If the operation at each PE is different, then some means must be used to distinguish which action is to be done at each PE. This is case 4. If no such distinguishing condition is used, it is really case 1.

Similarly, case 3 does not truly exist. For, if the condition is local but each PE does the same operation, then the local conditions must all be the same. This is really case 1.

A solution to our original range limiting problem would be an example of case 4. How do we do different operations at different PEs? To accomplish this, we make a simplification. Instead of performing different operations at different PEs, we perform or not perform an operation at each PE. We tell some PEs to do the operation and others to not do it. We can perform different operations at different PEs by simply selecting a different subset of the PEs for each different operation.

Think of a BitPlane containing a value of one at every PE that is to perform an operation and a zero for every PE that is not to perform that same operation. For our original example, the BitPlane created by the expression x < 0 would satisfy our needs. With this BitPlane it is only necessary to associate it with the operation that it will control.

We make that association with the Select declaration(1). The Select declaration specifies those PEs that are to do the operations given inside of the Select block. We call this set of PEs the active PEs and talk of this as the activity. Our original problem is now solved by the following statements.

    {Select active(x < 0) ;
      x = 0;
    }
    {Select active(x > 10);
      x = 10;
    }
The effect of these operations is illustrated in the following figure showing the grid of elements for plane x before and after the statements are executed. Note that only some of the values in x are changed.

The activity specifies which PEs are active. That is, the activity specifies which PEs will perform an operation. The active PEs are always a subset of the complete set of PEs. This subset may include all, some, or none of the complete set.

Readers familiar with C++ will note that the name active, used in the Select declaration, is actually the name of a variable. We could use any name for this variable! However, we urge you to always use the name active.
By being consistent, you will avoid errors that result from defining the activity twice in one block because the compiler will be able to detect this (multiple definition of a variable). And, other people will have an easier time reading your programs.

We can nest the activity selection to create subsets. For example, consider the problem ``for all (xyx greater than 5 by 3 and divide all values of y greater than 5 by 2''. This may be written as

    {Select active((x + y) > 10);
      {Select active(x > 5);
        x = x / 3;
      }
      {Select active(y > 5);
        y = y / 2;
      }
    }
The following figure illustrates this example with the shaded area representing the set of PEs for which (x + y) is greater than 10. That portion of the shaded area that is in the ``x > 5'' circle represents the set of PEs selected by the {Select active(x > 5); ... } block in the code above.

Obviously, this code could have been written as

    {Select active(((x + y) > 10) & (x > 5));
        x = x / 3;
    }
    {Select active(((x + y) > 10) & (y > 5));
        y = y / 2;
    }
But, allowing the nesting/scoping of activity makes programming easier and allows the activity to be usefully set outside of a function as in
    {Select active((x != 0) & (y != 0));
        complex_function_call(arg1, arg2, arg3);
    }
There are several of things to remember about activity. Regardless of the number of active PEs, the computation takes the same amount of time. If there are no PEs selected, a computation takes as long as when every PE is selected.

When activities are nested, the planes used to set the activities must be compatible.

The activity affects only the assignment operator(2). Activity can not be used to avoid exceptions such as division by zero. For example, the statements

    {Select active(x != 0);
        z = y / x;
    }
will not avoid the division by zero but only the assignment to z of the erroneous results(3).

Because activity affects assignment it is important for you to make sure that PEs not selected for an assignment operation will eventually contain an expected value. In the code below, not all the PEs have a known value for x.

    {Select active(y > 3);
        ShortPlane x(ps);
        x = y + z;
        ...
    }
In the chapter on arithmetic, we showed a statement to add 3 to every value of a plane that was not zero.
    x += (x != 0) * 3;
The following code has the same effect.
    {Select active(x != 0); x += 3; }
When a program begins, all PEs are active. The Select declaration is used to create a smaller subset. Sometimes it is useful to reselect every PE for a limited scope. The Everywhere declaration may be used for this. An example would be a function that needs to initialize a result everywhere regardless of the activity that existed when it was called.
    BitPlane some_function(...)
    {
        BitPlane overflow(ps);
        {Everywhere active;
            overflow = 0;
        }
        ...
        overflow = ...
        return overflow;
    }
Sometimes it is useful to select those PEs that are not currently active. We use the SelectNot declaration for this. Note that
    {Select active(y == 1); // Case A
        {Select active(x == 1);
            z = 0;
            {SelectNot active;
                z= 1;
            }}}
is not equivalent to
    {Select active(y == 1); // Case B
        {Select active(x == 1); 
            z = 0;
        }
        {Select active(x != 1);
            z = 1;
        }}
These two cases are illustrated in the following figure. The shaded area denotes those PEs that will have z set to 1.

All planes that are the destination of an assignment statement that occurs inside of a Select or SelectNot block must be compatible with the plane used to set the activity. This restriction does not hold for planes used inside of blocks with an Everywhere declaration.

Finally, you should remember that there is some overhead incurred with activity. Assignment operations performed when Everywhere active are the most efficient. Assignment operations are more efficient if the activity is defined in the lexical scope of a function.

Logical operations on BitPlanes are more efficient if an activity block can be avoided as shown below.

    x = (a < b);
is better than
    x = 0;
    {Select active (a < b);
        x = 1;
    }


Next -- Global Response
ICL Table of Contents
ICL Index
ICL Home Page


Notes

  1. The Select is a user-defined class designator. The syntax used is dictated by our desire to not modify the C++ language and the facilities available through C++ classes.
  2. Activity affects only assignment because of the difficulty that would result in describing the effects of activity if it were defined otherwise.
  3. One way to avoid division by zero is to eliminate the zero values in the divisor.
        {Select active(x == 0);  x = 1; }
        z = y / x;