5 - ParallelAlgorithms
5 - ParallelAlgorithms
1
Problem
• Assume we have a 100-inch sandwich to feed 8 people.
• It is easy to see that prefix sums can be computed sequentially in O(N) time
Naïve Inclusive Parallel Prefix Sum
• Assign one thread to calculate each 𝑌 element that adds up all X elements
needed for that Y element.
• 𝑌0 = 𝑋0
• 𝑌1 = 𝑋0 + 𝑋1
• 𝑌2 = 𝑋0 + 𝑋1 + 𝑋2
• Implementation:
#pragma omp parallel for
for (i = 0; i < N; i++){
Y[i] = 0;
for (j = 0; j <= i; j++)
Y[i] += X[j];
}
4
Naïve Inclusive Parallel Prefix Sum
10 26 36 52 66 68 76
6 4
10 16 26 10 36 16 52 14 66 2 68 8
6 4 52 14 66 2
10 16 26 10 36 16
6 4 36 16 52 14
10 16 26 10
6 4 26 10 36 16
10 16
6 4 10 26 10
16
• Observations: 6 4
10 16
• Total Work T1 (number of operations) = O(N2)
• Critical Path Length (CPL) = O(N) 6 4
• With P = O(N) processors, best achievable execution time is T1 = max(CPL, WORK/P) = O(N) 5
6
Parallel Prefix Sum: Upward Sweep
76
for (i = 0; i < n - 1; i++)
Y[i] = X[i]; 36 40
for (d = 1; d < n; d *= 2) {
#pragma omp parallel for 10 26 30 10
for (int i = 0; i < n; i += 2 * d) {
Y[i + 2 * d - 1] += Y[i + d - 1];
} 6 4 16 10 16 14 2 8
}
Step 6 4 16 10 16 14 2 8
d =1 6 10 16 26 16 30 2 10
d=2 6 10 16 36 16 30 2 40
7
d=4 6 10 16 36 16 30 2 76
Parallel Prefix Sum: Downward Sweep
0
Y[n-1] = 0;
for (d = n / 2; d >= 1; d /= 2) {
#pragma omp parallel for 0 36
for (int i = 0; i < n; i += 2 * d) {
int left = i + d - 1;
int right = i + 2 * d - 1; 0 10 36 66
temp = Y[left];
Y[left] = Y[right];
Y[right] += temp; 0 6 10 26 36 52 66 68
}
}
Step 6 10 16 36 16 30 2 76
0
d=4 6 10 16 0 16 30 2 36
d=2 6 0 16 10 16 36 2 66 8
d=1 0 6 10 26 36 52 66 68
Parallel Prefix Sum: Inclusive
for (i = 0; i < n; i++) {
Y[i] += X[i]; X 6 4 16 10 16 14 2 8
}
Y 0 6 10 26 36 52 66 68
Updated Y 6 10 26 36 52 66 68 76
• Observations:
• Critical Path Length (CPL) = O(log n)
• Total Work T1 (number of add operations) = O(N)
• Optimal algorithm for P = O(n / log n) processors
• Adding more processors does not help
• The total number of adds is no more than twice that done in the efficient sequential algorithm
• The benefit of parallelism can easily overcome the 2X work when there is sufficient hardware 9
Applications of Prefix Sum
• Parallel Prefix Sum has several applications that go way beyond computing the
• sum of array elements. A key primitive in many parallel algorithms to convert
serial computation into parallel computation
• Radix Sort
• String Comparison
• Lexical Analysis
• Stream Compaction
• Polynomial Evaluation
• Solving Recurrences
• Tree Operations
• Histograms
• Assigning space in farmers market
• Allocating memory to parallel threads
10
• Allocating memory buffer for communication channels
• Frequently used for parallel work assignment and resource allocation
Sorting in Parallel
• Why?
• It is a frequent operation in many applications
• Goal?
• Sorting a sequence of values in increasing order using n processors
11
Bubble Sort
• Idea: Given a list of n elements, repeat the following steps n -1 times:
• For each pair of adjacent numbers, if number on the left is greater than the number
on the right, swap them.
• “Bubble” the largest element to the end of the array using pair-wise comparisons
and swapping.
BUBBLE-SORT(A)
n ← length[A]
for i ← 0 to n – 2
for j ← 0 to n – i – 2
if A[ j ] > A[ j+1 ]
A[ j ] ↔ A[ j+1 ]
12
• Time complexity is O(N2) independent of the order of the elements.
Parallel Bubble Sort: Odd-Even Sort
• Idea: Rearrange the comparisons in as many independent comparisons as
possible!
• This can be achieved if the processors are grouped into even/odd pairs or
odd/even pairs
• Odd–Even Phase: The odd processes p compare and exchange their elements
with the even processors p + 1
• Even–Odd Phase: The even processes compare and exchange their elements with
the odd processors p + 1
13
Odd-Even Sort
4 2 7 8 5 1 3 6
2 4 7 8 1 5 3 6
2 4 7 1 8 3 5 6
2 4 1 7 3 8 5 6
2 1 4 3 7 5 8 6
1 2 3 4 5 7 6 8
14
1 2 3 4 5 6 7 8
Odd-Even Sort
OddEvenSort(A)
n = length[A]
for (i = 0; i <= n - 1; i++)
if( i%2 == 0)
#pragma omp parallel for
for (j = 0; j <= n - 2; i+=2)
if A[j] > A[j+1]
A[j] ↔ A[j+1]
else
#pragma omp parallel for
for (j = 1; j <= n - 2; i+=2)
if A[j] > A[j+1]
15
A[j] ↔ A[j+1]
barrier();
Odd-Even Sort
• Assuming array size n and p threads
• (assuming p ≤ n/2, i.e., each processor handles multiple comparisons)
9 3 17 11 6 2 1 10
divide
9 3 17 11 6 2 1 10
9 3 17 11 6 2 1 10
3 9 11 17 2 6 1 10
merge
3 9 11 17 1 2 6 10 17
1 2 3 6 9 10 11 17
Merge Sort
• Idea: Recursively divide a given unsorted list of n elements until each sublist
contains one element. Then these sublists are repeatedly merged to form a
sorted list.
MERGE-SORT(A, left, right) MERGE (A, left, mid, right)
if left < right i = left, j = mid+1, k=0
mid = (𝑙𝑒𝑓𝑡 + 𝑟𝑖𝑔ℎ𝑡)/2 while (i ≤ 𝑚𝑖𝑑 && 𝑗 ≤ right)
MERGE-SORT(A, left, mid) if (A[i] ≤ A[j])
MERGE-SORT(A, mid+1, right) B[k++] = A[i++]
MERGE (A, left, mid, right) else
B[k++] = A[j++]
A[ ] = B[ ]
9 3 17 11 6 2 1 10
P1 P0
9 3 17 11 6 2 1 10
P2 P1 P3 P0
9 3 17 11 6 2 1 10
9 3 17 11 6 2 1 10 P4 P2 P5 P1 P6 P3 P7 P0
3 9 11 17 2 6 1 10
P2 P1 P3 P0
3 9 11 17 1 2 6 10
P1 P0
1 2 3 6 9 10 11 17
20
P0
Odd-Even Merge Sort
• A parallel approach to sorting based on the recursive application of the odd-
even merge algorithm, to speed up the merge process.
21
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
22
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
23
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 11 9 17 2 1 6 10
24
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 11 9 17 2 1 6 10
25
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 2 1 6 10
26
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 2 1 6 10
27
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
28
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
29
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
3 1 9 2 11 6 17 10
30
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
3 1 9 2 11 6 17 10
31
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
1 3 2 9 6 11 10 17
32
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
1 3 2 9 6 11 10 17
33
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
1 2 3 6 9 10 11 17
34
Odd-Even Merge Sort
• Step 1: Recursive Division 9 3 17 11 6 2 1 10
3 9 11 17 1 2 6 10
1 2 3 6 9 10 11 17
• Time complexity is O(N lg N) and O(N2) in average and worst case, respectively.
Parallel Quick Sort
• Idea: Use a pivot and partition the array into two parts. Sort each of them
recursively, in parallel.
38
Parallel Quick Sort
• Step 1: Choose a Pivot 9 3 17 11 6 2 1 10 pivot = 9
39
Parallel Quick Sort
• Step 1: Choose a Pivot 9 3 17 11 6 2 1 10 pivot = 9
0 0 1 1 1 2 3 4 prefix_less
0 1 1 2 3 3 3 3 prefix_more
total_less = 4
40
Parallel Quick Sort
• Step 1: Choose a Pivot 9 3 17 11 6 2 1 10 pivot = 9
⋮
• Step 4: Scatter Values to
Output Array 1 2 3 6 9 10 11 17
47
Shell Sort
ShellSort(A)
n = length[A];
for (gap = n/2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
key = A[i];
j = i;
while(j > gap – 1 && A[j – gap] > key)
A[j] = A[j – gap];
j = j – H;
A[j] = key;
9
6
1 3
2 17 11
1 10
6
3 3
6 6
9 2
10 1 10
3 17
11 11
17
11 1
Gap = 4
2 48
Parallel Shell Sort
ShellSort(A)
n = length[A];
for (gap = n/2; gap > 0; gap /= 2)
#pragma omp parallel for shared(A, n, gap)
for (i = gap; i < n; i++)
key = A[i];
j = i;
while(j > gap – 1 && A[j – gap] > key)
A[j] = A[j – gap];
Total Work: O(n log2 n)
j = j – H;
Best Case: O((n/p) log n)
A[j] = key;
Worst Case: O(n2/p)