Friday, April 10, 2015

Ada 2012 Aspect Clauses => Static Predicates

John English wrote a wonderful book titled Ada 95: The Craft of Object-Oriented Programming, which is available on line at http://archive.adaic.com/docs/craft/html/contents.htm. In chapter 3 of that book he explores many Ada programming fundamentals using a simple calculator program example. The code for that example is:
    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
        Operand  : Integer;
    begin
        Put ("Enter an expression: ");
        Get (Result);                                   --  1
        loop                                            --  2
            loop                                        --  3
                Get (Operator);
                exit when Operator /= ' ';
            end loop;
            if Operator = '.' then                      --  4
                Put (Result, Width => 1);               --  5
                exit;                                   --  6
            else
                Get (Operand);                          --  7
                case Operator is
                    when '+' =>
                        Result := Result + Operand;     --  8
                    when '-' =>
                        Result := Result - Operand;
                    when '*' =>
                        Result := Result * Operand;     --  9
                    when '/' =>
                        Result := Result / Operand;
                    when others =>
                        Put ("Invalid operator '");     -- 10
                        Put (Operator);
                        Put ("'");
                        exit;                           -- 11
                end case;
            end if;
        end loop;
        New_Line;
    end Calculator;
This program implements a simple left-to-right calculator that does not understand parentheses. Each calculation string is terminated with a period (full stop for those speaking the King’s English).
1 + 2 * 3.
The calculation string above is evaluated left to right, yielding the value 9. The calculator does not impose any operator precedence.
The internal loop beginning with the line labeled 3 in an end-of-line comment deals with the fact that the Get procedure for type Character simply gets the next character in the input stream. It does not skip space characters to input the next non-space character. The loop implemented by John English simply reads characters until it encounters something that is not a space character. The hope implicit in this program is that the person creating the input strings only uses integral numbers, spaces, or one of the characters ‘+’, ‘-‘, ‘*’, ‘-‘, or ‘.’. If it encounters any other character it declares that an invalid operator has been input and terminates the program.
As a software safety engineer I am always uncomfortable with a program that cannot clearly and concisely specify simple input values. What happens, for instance, in the John English version of this program if the person running the program inputs horizontal tabs instead of spaces? They may look similar on an editor screen, but they are distinctly different to program.
I know that John English was well aware of these issues, but was trying to introduce students to the fundamentals of Ada programming rather than the complications of error handling. In fact, until the 2012 version of Ada there was no simple way to do better.
Ada 2012 introduced aspect clauses. The form of the aspect clause of interest to this article is the static predicate. A static predicate is a qualification of a type or subtype definition defining a set of properties about the type. In Ada 95 the best you could do was to specify the range of values for a subtype or type. That range had to be contiguous, with no gaps in the values. The Ada 2012 static predicate definition introduces more flexibility. It allows any expression that can be evaluated as a membership test.
How does this relate to the calculator program shown above? The operators in that program are five distinct characters, but not a contiguous range of characters. Let’s see how we could express that set of characters using an Ada 2012 static predicate.

Subtype Acceptable is Character
   with Static_Predicate => Acceptable in ‘+’|’-‘|’*’|’/’|’.’;

The result is a subtype of Character with five valid values.
Once we have established a subtype with only the few values we want to use, we have managed to more clearly express the intent of our program. A comparable program to the Calculator program above, but using this static predicate expression is:
------------------------------------------------------------------
-- Simple Calculator Program                                    --
------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_Io; use Ada.Integer_Text_IO;

procedure Simple_Calculator is
   subtype Acceptable is Character
   with Static_Predicate => Acceptable in '+'|'-'|'*'|'/'|'.';

   function Get_Operator return Acceptable is
      Temp : Character;
   begin
      loop
         get(Temp);
         exit when Temp in Acceptable;
      end loop;
      return Temp;
   end Get_Operator;

   Operator : Acceptable;
   Operand  : Integer;
   Result   : Integer;
begin
   Get(Item => Result);
   Outer:
   loop
      Operator := Get_Operator;

      if Operator = '.' then
         Put(Item => Result, Width => 1);
         New_Line;
         exit Outer when End_Of_File;
         Get(Item => Result);
      else
         Get(Item => Operand);
         case Operator is
         when '+' => Result := Result + Operand;
         when '-' => Result := Result - Operand;
         when '*' => Result := Result * Operand;
         when '/' => Result := Result / Operand;
         when '.' => null;
         end case;
      end if;
   end loop Outer;
end Simple_Calculator;

I have encapsulated the operator input loop into a function to clearly specify my intentions. Note that the function Get_Operator now loops until it reads a character that is a member of the subtype Acceptable. This logic will read past spaces, tabs, or any other characters that are not members of the subtype Acceptable. The case statement now does not need a “when Others” clause because it explicitly handles all possible values of subtype Acceptable. If another operator were to be added to the set of values for Acceptable the compiler would flag the need to handle the new value in the case statement.
The use of the Static Predicate to create a discontinuous set of valid values can be very useful. It also greatly simplifies programming over creating a more complex set of conditional statements on an input loop. Finally, it allows a more robust use of the case clause.

Monday, April 6, 2015

Parallel Addition Revisited

In an earlier article I discussed aspects of parallel addition of elements of an array. A more general problem would be to sum a set of numbers from a collection of numbers. The collection cannot be simply an arbitrary unending stream of numbers, because a sum of such a stream could never be completed. Instead, it must be some discrete sample size of numbers. Furthermore, the sample size cannot be zero. There must be at least one number in the sample or no sum is possible.

The following example collects a sample of numbers into a queue and then uses concurrent tasks to perform pair-wise addition of the queue elements. Each result of pair-wise addition is placed back on the queue. Eventually, given a discrete number of items in the queue, the queue will be left containing only one value. That value will be the total of all the operations on the set of numbers. This approach works because addition is commutative. The order the numbers are added is irrelevant to the solution.

Parallel Addition In Ada

The following source code accomplishes this more general parallel addition capability.
The key to the operation of the program is the protected object Queue, which contains the list of numbers shared by all the parallel tasks.
Each task performing addition calls the entry Get_Two, which dequeues two integers from the Queue object. The task then adds the two integers and calls the procedure Enqueue to place the result on the Queue object.

Parallel_Addition.ads
------------------------------------------------------------------
-- Parallel Addition Package                                    --
------------------------------------------------------------------
with Ada.Containers.Doubly_Linked_Lists;

package Parallel_Addition is
   Package Int_Queue is new Ada.Containers.Doubly_Linked_Lists(Integer);
   use Int_Queue;

   protected Queue is
      Entry Get_Two(Item1, Item2 : out Integer);
      Entry Dequeue(Item : out Integer);
      Procedure Enqueue(Item : in Integer);
      Procedure Clear;
      Function Count return Integer;
   private
      Nums : Int_Queue.List;
   end Queue;

   task type Adder;

end Parallel_Addition;

Inner workings of the Parallel_Addition package

I have chosen the Doubly Linked List package provided by the Ada container library to implement my queue. The Enqueue procedure appends values to the end of the list (queue) and both the Get_Two and Dequeue entries dequeue values from the beginning of the list (queue).

Ada entries have a boundary condition. The boundary condition for the Get_Two entry evaluates to TRUE when the list contains more than 1 element. The boundary condition for the Dequeue entry evaluates to TRUE when the list contains more than 0 elements. The Clear procedure empties the list. The Count function reports the number of tasks suspended on the Get_Two entry queue. Every task calling an entry will suspend until the boundary condition evaluates to TRUE. The tasks will then be allowed to execute the entry in FIFO order, ensuring that all tasks are allowed to reliably access the entry.

The Adder task type contains an infinite loop which simply calls Get_Two to dequeue two values from the Queue object, then Enqueues the sum of those two values onto the Queue. Since every pair of values is replaced with a single value, the size of the Queue rapidly gets smaller. Once there is only one value left in the Queue, the Adder tasks will all suspend waiting for a second value. The last value left on the Queue is the total of all the numbers placed on the Queue.

Parallel_Addition.adb
with Ada.Containers; use Ada.Containers;
package body Parallel_Addition is
   -----------
   -- Queue --
   -----------
   --------------------------------------------------------------------
   -- The Queue protected object treats its internal list as a queue.--
   -- All data is read from the head of the list and new data is     --
   -- appended to the end of the list. Reading data from the head    --
   -- also deletes that data from the queue.                         --
   --                                                                --
   -- The Count function reports the number of tasks waiting in the  --
   -- entry queue for the Get_Two entry.                             --
   -- The Clear procedure empties the internal list of its contents. --
   --------------------------------------------------------------------
   protected body Queue is
   -------------
   -- Get_Two --
   -------------
   entry Get_Two (Item1, Item2 : out Integer) when Nums.Length > 1 is
   begin
      Item1 := Nums.First_Element;
      Nums.Delete_First;
      Item2 := Nums.First_Element;
      Nums.Delete_First;
   end Get_Two;

   -------------
   -- Dequeue --
   -------------
   entry Dequeue (Item : out Integer) when Nums.Length > 0 is
   begin
      Item := Nums.First_Element;
      Nums.Delete_First;
   end Dequeue;

   -------------
   -- Enqueue --
   -------------
   procedure Enqueue (Item : in Integer) is
   begin
      Nums.Append(Item);
   end Enqueue;

   -----------
   -- Count --
   -----------

   function Count return Integer is
   begin
      Return Get_Two'Count;
   end Count;

   -----------
   -- Clear --
   -----------
   procedure Clear is
   begin
      Nums.Clear;
   end Clear;
   end Queue;

   -----------
   -- Adder --
   -----------
   ---------------------------------------------------------------------
   -- Each Adder task simply calls Get_Two to dequeue two values from --
   -- the queue. It then enqueues the sum of those two values back    --
   -- on the queue. The queue will contain the final sum when it      --
   -- contains only a single value and no tasks are holding the lock  --
   -- for the Get_Two entry.                                          --
   ---------------------------------------------------------------------
   task body Adder is
      Value1, Value2 : Integer;
   begin
      Loop
         Queue.Get_Two(Value1, Value2);
         Queue.Enqueue(Value1 + Value2);
      end loop;
   end Adder;
end Parallel_Addition;

A test driver for the Parallel_Addition package

The Parallel_Addition_Main procedure is my test driver for the Parallel_Addition package.
The subtype The_Nums is created to define a range of values to add. In the source code below the range of values is specified as -600 through 601 inclusive. The sum of this set of numbers should be 601.
An array of Adder tasks is created. The tasks start running immediately. Since there are no values in the Queue protected object, all the tasks will suspend in the Get_Two entry queue waiting for the boundary condition to evaluate to TRUE.
The test driver performs the addition 20 times. This is done to reveal any race conditions that might exist in the code. For each iteration of the test the Queue is cleared and the numbers are Enqueued into the Queue object. The test driver then polls the Queue.Count function to determine when all the Adder tasks are once again suspended in the Get_Two entry queue. That event indicates there is only one value left in the Queue, which is our total. That final value is Dequeued and displayed.
After the 20 iterations all the Adder tasks are aborted and the program ends in an orderly manner.

Parallel_Addition_Main.adb
------------------------------------------------------------------
-- Parallel Addition Main Program                               --
--                                                              --
-- This program uses a task pool contained in the array named   --
-- workers. The task pool is reused for multiple iterations.    --
-- The tasks are aborted at the end of the program.             --
------------------------------------------------------------------
with Parallel_Addition; use Parallel_Addition;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Task_Identification; use Ada.Task_Identification;

procedure Parallel_Addition_Main is
   subtype The_Nums is Integer range -600..601;
   Workers : array(1..3) of Adder;
   Total : Integer;
begin
   for I in 1..20 loop
      Put(I'Img & " ");
      Queue.Clear;
      for I in The_Nums'Range loop
         Queue.Enqueue(I);
      end loop;

      Loop
         exit when Queue.Count = Workers'Length;
      end loop;

      Queue.Dequeue(Total);
      Put_Line("The total of the series of numbers from " &
                The_Nums'First'Img & " to " &
                The_Nums'Last'Img & " is " &
                Total'Img);
   end loop;

   for Worker of Workers loop
      Abort_Task(Worker'Identity);
   end loop;
end Parallel_Addition_Main;


Program output
 1 The total of the series of numbers from -600 to  601 is  601
 2 The total of the series of numbers from -600 to  601 is  601
 3 The total of the series of numbers from -600 to  601 is  601
 4 The total of the series of numbers from -600 to  601 is  601
 5 The total of the series of numbers from -600 to  601 is  601
 6 The total of the series of numbers from -600 to  601 is  601
 7 The total of the series of numbers from -600 to  601 is  601
 8 The total of the series of numbers from -600 to  601 is  601
 9 The total of the series of numbers from -600 to  601 is  601
 10 The total of the series of numbers from -600 to  601 is  601
 11 The total of the series of numbers from -600 to  601 is  601
 12 The total of the series of numbers from -600 to  601 is  601
 13 The total of the series of numbers from -600 to  601 is  601
 14 The total of the series of numbers from -600 to  601 is  601
 15 The total of the series of numbers from -600 to  601 is  601
 16 The total of the series of numbers from -600 to  601 is  601
 17 The total of the series of numbers from -600 to  601 is  601
 18 The total of the series of numbers from -600 to  601 is  601
 19 The total of the series of numbers from -600 to  601 is  601
 20 The total of the series of numbers from -600 to  601 is  601


Comparison of three algorithms for summing an array of integers

This article shares a comparison of the time taken to sum an array of integers. This article tests three algorithms for summing the array. •...