Parallelism: Ada 2022 parallel Constructs & Task-Based Chunking
Learn the Ada 2022 parallel block, parallel loop and parallel reduction syntax (and its data-race rules), check what your compiler supports, and implement portable data parallelism today with chunked worker tasks, per-chunk partial results and 'Reduce, plus System.Multiprocessors and CPU affinity.
Tasks express concurrency — independent activities. Ada 2022 adds lightweight parallelism: 'parallel' blocks and loops that split a computation into logical threads of control managed by the runtime, with compile-time rules against data races. Compiler support is still arriving, so this lesson teaches the standard syntax and the portable task-based technique that works with every Ada compiler.
Ada 2022 Parallel Syntax
A parallel block runs its arms concurrently: 'parallel do Left := Sum (A (1 .. Mid)); and Right := Sum (A (Mid + 1 .. A'Last)); end do;'. A parallel loop: 'parallel for I in A'Range loop A (I) := F (I); end loop;', optionally with a chunk specification 'parallel (Chunk in 1 .. 8) for I in ...'. Reductions: 'A'Parallel_Reduce ("+", 0)' or '[parallel for I in A'Range => A (I) ** 2]'Reduce ("+", 0)'. The language forbids conflicting accesses between logical threads (checked with the Conflict_Check_Policy) and specifies Global and Nonblocking aspects so compilers can verify subprograms are safe to call in parallel.
Compiler status: GNAT FSF 16.1, used for every example in this course, does not yet implement parallel blocks and loops. It parses the word parallel as an identifier and reports syntax errors, as the compiler output below shows. Check your compiler's release notes before relying on these constructs.
• parallel do ... and ... end do; — parallel block. • parallel [(Chunk in R)] for I in ... loop — parallel loop. • 'Parallel_Reduce / [parallel for ...]'Reduce — parallel reductions. • Conflict checks, Global and Nonblocking aspects prevent races.
Portable Chunked Parallelism with Tasks
The same effect is achieved with a small pool of worker tasks: split the index range into chunks (one per CPU is a good start — System.Multiprocessors.Number_Of_CPUs tells you how many), let each task compute a partial result into its own slot, join by leaving the master scope, then combine the partial results (Partial'Reduce ("+", 0)). No locks are needed because no two tasks write the same variable. For long-lived pools, use protected work queues; the CPU aspect and System.Multiprocessors.Dispatching_Domains pin tasks to processors. Libraries such as Paraffin (via Alire) provide generic parallel loops and reductions.
• Chunk the range; one task per chunk; per-chunk results. • Master scope exit = join; then reduce. • Number_Of_CPUs; CPU aspect / dispatching domains for affinity.
Code Examples
Ada 2022 parallel loop syntax. Real diagnostics from GNAT FSF 16.1, which does not yet support it: l57_err.adb:5:12: error: missing ":" l57_err.adb:7:04: error: "end loop parallel;" expected
The portable version: four chunk workers compute the sum of squares of (I mod 100) over one million elements, and the parallel reduction matches the sequential loop exactly. Output: CPUs available > 0? TRUE Partial sums: 820875000 820875000 820875000 820875000 Parallel reduction = 3283500000 Sequential result = 3283500000 -- each worker does Total:= Total + X; on a shared variable -- each worker writes Partial (Id); combine after the join
Shared accumulators race; partial results need no synchronization.
-- one task per element -- a few chunks (≈ number of CPUs) Task creation has overhead; chunk the work.
-- assuming 'parallel for' compiles everywhere -- check compiler support; keep a task-based fallback
Ada 2022 parallel constructs are not yet implemented by all compilers (verified with GNAT 16.1).
What does 'parallel do A; and B; end do;' mean in Ada 2022? A. Run A then B B. A and B may run concurrently as logical threads C. Run A and B in separate processes D. Syntax error
Why store results in Partial (Id) instead of a shared Total? A. Speed of access B. Avoid data races without locks C. Required by syntax D. Save memory
Which function returns the number of processors? A. System.Multiprocessors.Number_Of_CPUs B. Ada.Task_Count C. System.CPUs D. Ada.Real_Time.CPUs
Does GNAT FSF 16.1 compile 'parallel for'? A. Yes B. No — it reports syntax errors (verified) C. Only with -O3 D. Only on Linux Trace the chunked parallel example.
Real output: CPUs available > 0? TRUE Partial sums: 820875000 820875000 820875000 820875000 Parallel reduction = 3283500000 Sequential result = 3283500000 Each chunk of 250 000 elements contains 2 500 full cycles of 0 .. 99, whose squares sum to 328 350, so each partial is 820 875 000.
with Ada.Text_IO; use Ada.Text_IO;
procedure L57_Err is
A : array (1 .. 8) of Integer := [others => 0];
begin
parallel for I in A'Range loop
A (I) := I * I;
end loop;
Put_Line (Integer'Image (A (8)));
end L57_Err;with Ada.Text_IO; use Ada.Text_IO;
with System.Multiprocessors; use System.Multiprocessors;
procedure L57_Parallel is
N : constant := 1_000_000;
type Data_Array is array (1 .. N) of Long_Long_Integer;
type Data_Access is access Data_Array;
Data : constant Data_Access := new Data_Array;
Chunks : constant := 4;
Partial : array (1 .. Chunks) of Long_Long_Integer := [others => 0];
-- Emulating "parallel for" with a fixed pool of worker tasks (chunking)
task type Chunk_Worker (Id : Positive);
task body Chunk_Worker is
First : constant Positive := (Id - 1) * (N / Chunks) + 1;
Last : constant Positive := Id * (N / Chunks);
Sum : Long_Long_Integer := 0;
begin
for I in First .. Last loop
Sum := Sum + Data (I) * Data (I);
end loop;
Partial (Id) := Sum; -- no shared writes: each chunk has its own slot
end Chunk_Worker;
Sequential : Long_Long_Integer := 0;
begin
Put_Line ("CPUs available > 0? " & Boolean'Image (Number_Of_CPUs > 0));
for I in Data'Range loop
Data (I) := Long_Long_Integer (I mod 100);
end loop;
declare
W1 : Chunk_Worker (1);
W2 : Chunk_Worker (2)
…