2 3 parent thread fork join start end child threads compute time overhead

58
How Shared Memory Parallelism Behaves

Upload: gerard-casey

Post on 26-Dec-2015

218 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

How Shared Memory Parallelism Behaves

Page 2: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

The Fork/Join Model

Many shared memory parallel systems use a programming model called Fork/Join. Each program begins executing on just a single thread, called the parent.

Fork: When a parallel region is reached, the parent thread spawns additional child threads as needed.

Join: When the parallel region ends, the child threads shut down, leaving only the parent still running.

2

Page 3: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

The Fork/Join Model (cont’d)

3

Parent Thread

Fork

Join

Start

End

Child ThreadsC

ompu

te ti

me

Overhead

Overhead

Page 4: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

The Fork/Join Model (cont’d)

In principle, as a parallel section completes, the child threads shut down (join the parent), forking off again when the parent reaches another parallel section.

In practice, the child threads often continue to exist but are idle.

Why?

4

Page 5: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Principle vs. Practice

5

Fork

Join

Start

End

Fork

Join

Start

End

Idle

Page 6: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Why Idle?

On some shared memory multithreading computers, the overhead cost of forking and joining is high compared to the cost of computing, so rather than waste time on overhead, the children sit idle until the next parallel section.

On some computers, joining threads releases a program’s control over the child processors, so they may not be available for more parallel work later in the run. Gang scheduling is preferable, because then all of the processors are guaranteed to be available for the whole run.

6

Page 7: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

OpenMP

Page 8: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

What Is OpenMP?

Portable, shared-memory threading API Fortran, C, and C++ Multi-vendor support for both Linux and

Windows Standardizes task & loop-level

parallelism Supports coarse-grained parallelism Combines serial and parallel code in

single source Standardizes ~ 20 years of compiler-

directed threading experience

8

http://www.openmp.orgCurrent spec is OpenMP 3.0

318 Pages

(combined C/C++ and Fortran)

Page 9: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

9

Programming Model

Fork-Join Parallelism: Master thread spawns a team of

threads as needed Parallelism is added incrementally:

that is, the sequential program evolves into a parallel program

Parallel Regions

Master Thread

Page 10: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

A Few Syntax Details to Get Started

Most of the constructs in OpenMP are compiler directives or pragmas For C and C++, the pragmas take the

form: #pragma omp construct [clause [clause]…]

Header file #include <omp.h>

10

Page 11: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Agenda

What is OpenMP? Parallel regions Worksharing Data environment Synchronization

11

Page 12: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Parallel Region & Structured Blocks (C/C++)

Most OpenMP constructs apply to structured blocks Structured block: a block with one point of entry

at the top and one point of exit at the bottom The only “branches” allowed are exit() in C/C++

Bad: break; goto

OK: continue; (usually)

12

Page 13: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

13

Code 1: Hello World(s)

Page 14: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Worksharing

Worksharing is the general term used in OpenMP to describe distribution of work across threads.

Three examples of worksharing in OpenMP are: omp for construct omp sections construct omp task construct

14

Automatically divides work among threads

Page 15: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

omp for Construct

Threads are assigned an independent set of iterations

Threads must wait at the end of work-sharing construct

15

#pragma omp parallel

#pragma omp for

Implicit barrier

i = 1

i = 2

i = 3

i = 4

i = 5

i = 6

i = 7

i = 8

i = 9

i = 10

i = 11

i = 12

// assume N=12

#pragma omp parallel#pragma omp forfor(i = 1, i < N+1, i++) c[i] = a[i] + b[i];

Page 16: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Combining constructs

These two code segments are equivalent

16

#pragma omp parallel { #pragma omp for for (i=0;i< MAX; i++) { res[i] = huge(); } }

#pragma omp parallel for for (i=0;i< MAX; i++) { res[i] = huge(); }

Page 17: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

The Private Clause

Reproduces the variable for each task Variables are un-initialized; C++ object

is default constructed Any value external to the parallel region

is undefined

17

void* work(float* c, int N) { float x, y; int i; #pragma omp parallel for private(x,y) for(i=0; i<N; i++) {

x = a[i]; y = b[i]; c[i] = x + y; }}

Page 18: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

The schedule clause

The schedule clause affects how loop iterations are mapped onto threads

schedule(static [,chunk]) Blocks of iterations of size “chunk” to threads Round robin distribution Low overhead, may cause load imbalance

schedule(dynamic[,chunk]) Threads grab “chunk” iterations When done with iterations, thread requests next set Higher threading overhead, can reduce load imbalance

schedule(guided[,chunk]) Dynamic schedule starting with large block Size of the blocks shrink; no smaller than “chunk”

18

Page 19: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Schedule Clause Example

19

#pragma omp parallel for schedule (static, 8) for( int i = start; i <= end; i += 2 ) { if ( TestForPrime(i) ) gPrimesFound++; }

Iterations are divided into chunks of 8• If start = 3, then first chunk is i={3,5,7,9,11,13,15,17}

Page 20: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Data Scoping – What’s shared

OpenMP uses a shared-memory programming model

Shared variable - a variable whose name provides access to a the same block of storage for each task region Shared clause can be used to make items

explicitly shared Global variables are shared among tasks

C/C++: File scope variables, namespace scope variables, static variables, Variables with const-qualified type having no mutable member are shared, Static variables which are declared in a scope inside the construct are shared.

20

Page 21: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Data Scoping – What’s private

But, not everything is shared...

Examples of implicitly determined private variables: Stack (local) variables in functions called from

parallel regions are PRIVATE Automatic variables within a statement block are

PRIVATE (certain) Loop iteration variables are private Implicitly declared private variables within tasks

will be treated as firstprivate

Firstprivate clause declares one or more list items to be private to a task, and initializes each of them with a value

21

Page 22: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

22

A Data Environment Example

temp

A, index, count

temp temp

A, index, count

Which variables are shared and which variables are private?

float A[10];

main ()

{

integer index[10];

#pragma omp parallel

{

Work (index);

}

printf (“%d\n”, index[1]);

}

extern float A[10];

void Work (int *index)

{

float temp[10];

static integer count;

<...>

}

A, index, and count are shared by all threads, but temp is local to

each thread

Page 23: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Example: Dot Product

23

float dot_prod(float* a, float* b, int N) { float sum = 0.0;#pragma omp parallel for for(int i=0; i<N; i++) { sum += a[i] * b[i]; } return sum;}

What is Wrong?

Page 24: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Race Condition

A race condition is nondeterministic behavior caused by the times at which two or more threads access a shared variable

For example, suppose both Thread A and Thread B are executing the statement:

area += 4.0 / (1.0 + x*x);

24

Page 25: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Two Timings

25

Value of area

Thread A Thread B

11.667

+3.765

15.432

15.432

+ 3.563

18.995

Value of area

Thread A Thread B

11.667

+3.765

11.667

15.432

+ 3.563

15.230

Order of thread execution causes non determinant behavior in a data race

Page 26: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Protect Shared Data

Must protect access to shared, modifiable data

26

float dot_prod(float* a, float* b, int N) { float sum = 0.0;#pragma omp parallel for for(int i=0; i<N; i++) {#pragma omp critical sum += a[i] * b[i]; } return sum;}

Page 27: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

OpenMP Critical Construct

#pragma omp critical [(lock_name)] Defines a critical region on a structured

block

27

float RES;#pragma omp parallel{ float B; #pragma omp for for(int i=0; i<niters; i++){ B = big_job(i);#pragma omp critical (RES_lock) consum (B, RES); }}

Threads wait their turn –at a time, only one calls consum() thereby protecting RES from race conditions

Naming the critical construct RES_lock is optional

Good Practice – Name all critical sections

Page 28: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

OpenMP Reduction Clause

reduction (op : list) The variables in “list” must be shared in

the enclosing parallel region Inside parallel or work-sharing

construct: A PRIVATE copy of each list variable is

created and initialized depending on the “op”

These copies are updated locally by threads

At end of construct, local copies are combined through “op” into a single value and combined with the value in the original SHARED variable

28

Page 29: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Reduction Example

Local copy of sum for each thread All local copies of sum added together

and stored in “global” variable

29

#pragma omp parallel for reduction(+:sum) for(i=0; i<N; i++) { sum += a[i] * b[i]; }

Page 30: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

C/C++ Reduction Operations

A range of associative operands can be used with reduction

Initial values are the ones that make sense mathematically

30

Operand Initial Value

+ 0

* 1

- 0

^ 0

Operand Initial Value

& ~0

| 0

&& 1

|| 0

Page 31: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Numerical Integration Example

31

4.0

2.0

1.00.0

4.0

(1+x2)f(x) =

X

4.0

(1+x2) dx = 0

1

static long num_steps=100000; double step, pi;

void main(){ int i; double x, sum = 0.0;

step = 1.0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0.5)*step; sum = sum + 4.0/(1.0 + x*x); } pi = step * sum; printf(“Pi = %f\n”,pi);}

Page 32: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Activity 5 - Computing Pi

Parallelize the numerical integration code using OpenMP

What variables can be shared? What variables need to be private? What variables should be set up for

reductions?

32

Page 33: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Single Construct

Denotes block of code to be executed by only one thread First thread to arrive is chosen

Implicit barrier at end

33

#pragma omp parallel{ DoManyThings();#pragma omp single { ExchangeBoundaries(); } // threads wait here for single DoManyMoreThings();}

Page 34: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Master Construct

Denotes block of code to be executed only by the master thread

No implicit barrier at end

34

#pragma omp parallel{ DoManyThings();#pragma omp master { // if not master skip to next stmt ExchangeBoundaries(); } DoManyMoreThings();}

Page 35: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Implicit Barriers

Several OpenMP constructs have implicit barriers Parallel – necessary barrier – cannot

be removed for single

Unnecessary barriers hurt performance and can be removed with the nowait clause The nowait clause is applicable to:

For clause Single clause

35

Page 36: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Nowait Clause

Use when threads unnecessarily wait between independent computations

36

#pragma single nowait

{ [...] }

#pragma omp for nowait

for(...)

{...};

#pragma omp for schedule(dynamic,1) nowait for(int i=0; i<n; i++) a[i] = bigFunc1(i);

#pragma omp for schedule(dynamic,1) for(int j=0; j<m; j++) b[j] = bigFunc2(j);

Page 37: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Barrier Construct

Explicit barrier synchronization Each thread waits until all threads

arrive

37

#pragma omp parallel shared (A, B, C) {

DoSomeWork(A,B);printf(“Processed A into B\n”);

#pragma omp barrier DoSomeWork(B,C);printf(“Processed B into C\n”);

}

Page 38: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Atomic Construct

Special case of a critical section Applies only to simple update of memory

location

38

#pragma omp parallel for shared(x, y, index, n) for (i = 0; i < n; i++) { #pragma omp atomic x[index[i]] += work1(i); y[i] += work2(i); }

Page 39: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Task Decomposition

39

a = alice(); b = bob(); s = boss(a, b); c = cy(); printf ("%6.2f\n", bigboss(s,c));

alice,bob, and cy can be computed

in parallel

alice bob

boss

bigboss

cy

Page 40: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

omp sections

#pragma omp sections Must be inside a parallel region Precedes a code block containing of N blocks

of code that may be executed concurrently by N threads

Encompasses each omp section

#pragma omp section Precedes each block of code within the

encompassing block described above May be omitted for first parallel section after

the parallel sections pragma Enclosed program segments are distributed

for parallel execution among available threads

40

Page 41: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Functional Level Parallelism with sections

41

#pragma omp parallel sections{ #pragma omp section /* Optional */ a = alice();#pragma omp section b = bob();#pragma omp section c = cy();}

s = boss(a, b);printf ("%6.2f\n", bigboss(s,c));

Page 42: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Advantage of Parallel Sections

Independent sections of code can execute concurrently – reduce execution time

42

Serial Parallel

#pragma omp parallel sections

{

#pragma omp section

phase1();

#pragma omp section

phase2();

#pragma omp section

phase3();

}

Page 43: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Tasks - New Addition to OpenMP 3.0 Tasks – Main change for OpenMP 3.0

Allows parallelization of irregular problems unbounded loops recursive algorithms producer/consumer

43

Page 44: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

What are tasks?Tasks are independent units of

work Threads are assigned to

perform the work of each task Tasks may be deferred Tasks may be executed

immediatelyThe runtime system decides

which Tasks are composed of:

code to execute data environment internal control variables

(ICV)44

Serial Parallel

Page 45: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Simple Task Example

45

A pool of 8 threads is created here

#pragma omp parallel// assume 8 threads{ #pragma omp single private(p) { … while (p) { #pragma omp task { processwork(p); } p = p->next; } }}

One thread gets to execute the while loop

The single “while loop” thread creates a task for

each instance of processwork()

Page 46: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Task Construct – Explicit Task View A team of threads is created at

the omp parallel construct A single thread is chosen to

execute the while loop – lets call this thread “L”

Thread L operates the while loop, creates tasks, and fetches next pointers

Each time L crosses the omp task construct it generates a new task and has a thread assigned to it

Each task runs in its own thread

All tasks complete at the barrier at the end of the parallel region’s single construct 46

#pragma omp parallel{ #pragma omp single { // block 1 node * p = head; while (p) { //block 2 #pragma omp task process(p); p = p->next; //block 3 } }}

Page 47: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Why are tasks useful?Have potential to parallelize irregular patterns and recursive function calls

#pragma omp parallel{ #pragma omp single { // block 1 node * p = head; while (p) { //block 2 #pragma omp task process(p); p = p->next; //block 3 } }}

Block1

Block 2Task 1

Block 2Task 2

Block 2Task 3

Block3

Time

Single Threade

dBlock1

Thr1 Thr2 Thr3 Thr4

Block 2Task 2

Block 2Task 1

Block 2Task 3

Time Saved

Idle

Idle

Block3

Block3

Block3

Page 48: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

48

When are tasks gauranteed to be complete?

Tasks are gauranteed to be complete:

• At thread or task barriers

• At the directive: #pragma omp barrier

• At the directive: #pragma omp taskwait

Page 49: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

49

Task Completion Example

#pragma omp parallel{

#pragma omp taskfoo();#pragma omp barrier#pragma omp single{

#pragma omp taskbar();

}}

Multiple foo tasks created here – one for each thread

All foo tasks guaranteed to be completed here

One bar task created here

bar task guaranteed to be completed here

Page 50: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

50CS 491 – Parallel and Distributed Computing

int main(int argc, char* argv[]){   [...]   fib(input);   [...]}

int fib(int n){   if (n < 2) return n;   int x, y;   x = fib(n - 1);   y = fib(n - 2);   return x+y;}

Page 51: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

51CS 491 – Parallel and Distributed Computing

int main(int argc, char* argv[]){   [...]#pragma omp parallel{#pragma omp single{   fib(input);}} /* end omp parallel */   [...]}

int fib(int n){   if (n < 2) return n;   int x, y;#pragma omp task shared(x)   x = fib(n - 1);#pragma omp task shared(y)   y = fib(n - 2);#pragma omp taskwait   return x+y;}

Page 52: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

52CS 491 – Parallel and Distributed Computing

while(p != NULL){ do_work(p->data); p = p->next;}

Page 53: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Activity 4 – Linked List using Tasks

Objective: Modify the linked list pointer chasing code to implement tasks to parallelize the application

Follow the Linked List task activity called LinkedListTask in the student lab doc

Note: We also have a companion lab, that uses worksharing to solve the same problem LinkedListWorkSharing

We also have task labs on recursive functions - examples quicksort & fibonacci

53

Page 54: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

54CS 491 – Parallel and Distributed Computing

Page 55: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

What Is OpenMP?

OpenMP is a standardized way of expressing shared memory parallelism.

OpenMP consists of compiler directives, functions and environment variables.

When you compile a program that has OpenMP in it, if your compiler knows OpenMP, then you get an executable that can run in parallel; otherwise, the compiler ignores the OpenMP stuff and you get a purely serial executable.

OpenMP can be used in Fortran, C and C++, but only if your preferred compiler explicitly supports it.

55

Page 56: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Compiler Directives

A compiler directive is a line of source code that gives the compiler special information about the statement or block of code that immediately follows.

C++ and C programmers already know about

compiler directives:

#include "MyClass.h"

56

Page 57: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

OpenMP Compiler Directives

In C++ and C, OpenMP directives look like:

#pragma omp …stuff…

This means “the rest of this line contains OpenMP information.”

Aside: “pragma” is the Greek word for “thing.” Go figure.

57

Page 58: 2 3 Parent Thread Fork Join Start End Child Threads Compute time Overhead

Example OpenMP Directives

Fortran!$OMP PARALLEL DO!$OMP CRITICAL!$OMP MASTER!$OMP BARRIER!$OMP SINGLE!$OMP ATOMIC!$OMP SECTION!$OMP FLUSH!$OMP ORDERED

C++/C#pragma omp parallel for#pragma omp critical#pragma omp master#pragma omp barrier#pragma omp single#pragma omp atomic#pragma omp section#pragma omp flush#pragma omp ordered

58