generic parallel patterns in lime

0 downloads 0 Views 259KB Size Report
Nov 3, 2014 - public static void output(T[NN] a) {. 52. System . out . println ..... public static void main( String [] args ) {. 10.
G ENERIC PARALLEL PATTERNS IN L IME ∗ Emil Sekerinski1 and Tian Zhang2 [email protected], [email protected] of Computing and Software, McMaster University 2 IBM Canada Research and Development Centre

1 Department

FT

November 3, 2014

RA

Lime is a heterogeneous computing language currently being developed at IBM Research1 [Auerbach et al., 2010]. It is designed to be executable across a broad range of architectures, including CPUs, GPUs, and FPGAs. The novel features of Lime include limiting side-effects and integration of the streaming paradigm into an objectoriented language. Lime extends Java 1.6 as specified by [Gosling et al., 2005]. A design pattern is a reusable solution for a category of problems [Gamma et al., 1995]. Design patterns offer software developers a repertoire of proven solutions that can be tailored for needs. They are highly popular for object-oriented programming and are being used for concurrent and parallel programming. In this document we present a Lime generic implementation of nine parallel design patterns2 . The first seven patterns, namely fork-join, parallel generator, map, recurrence, stencil, reduction and scan, are taken from [McCool et al., 2012] except parallel generator (which is a variation of fork-join). The last two patterns, namely “divide and conquer differently” and “divide and conquer”, the latter one being a special case of the former one, are extracted from various applications. We divide the package into three parts: “common”, which defines patterns and operations that are used by other packages; “testcases”, which includes simple examples that can be used for testing the Lime environment; “examples”, which consists of practical problems that can be solved using the generic patterns. First of all, we give the list of patterns here, the details of which will be revealed later:

1

package common ;

2 3

public c l a s s G e n e r i c P a t t e r n s {

4

6 7 8 9 10 11 12 13

/∗ ∗ ∗ ∗ ∗

g e n e r i c t y p e names : TI : i n p u t t y p e TO : o u t p u t t y p e T : when i n p u t and o u t p u t t y p e s a r e t h e same TN : n u m e r i c t y p e w i t h ‘ ‘ + ’ ’ o p e r a t i o n , which i s n e c e s s a r y f o r s c a n p a t t e r n ∗ /

D

5



>

14 15



16 17



18 19

> ∗ This

document is prepared using Literate Programming for Eclipse: http://lep.sourceforge.net/. is currently in alpha stage, installation guide available at http://lime.watson.ibm.com/wiki/articles/t/h/e/The_Liquid_ Metal_Alpha_Release.html 2 Some patterns (e.g., naive map pattern) might be less efficient than Lime native implementation. 1 Lime

1

20



21 22



23 24



25 26



27 28 29

} I NCLUDED B LOCKS: 9 on page 9, 11 on page 10, 13 on page 11, 15 on page 13, 17 on page 14, 19 on page 15, 21 on page 16, 23 on page 18, 25 on page 20

Then we define some constants and operations that will be used in examples: package common ;

2 3

import lime . u t i l . PseudoRandom ;

4 5

public c l a s s T o o l k i t {

6 7 8

/ ∗ maximum random i n t e g e r ∗ / public s t a t i c f i n a l i n t MAX = 1 5 ;

9

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

/∗ t e s t public public public public public

messages ∗/ static final static final static final static final static final

string string string string string

INPUT RESULT REFERENCE TEST_PASS TEST_FAIL

= = = = =

" input : "; " result : "; " reference : " ; " TEST PASSED ! " ; " TEST FAILED ! " ;

RA

11

/ ∗ i n c r e a s e s an i n t e g e r by 1 ∗ / public s t a t i c l o c a l i n t i n c ( i n t i ) { return i + 1 ; }

/ ∗ g e n e r a t e 2 n e i g h b o r s on a c i r c u l a r a r r a y ∗ / public s t a t i c l o c a l ‘ ( i n t , i n t ) [ [NN] ] genNeighbors ( i n t [ [NN] ] a ) { ‘ ( i n t , i n t ) [NN] r e s u l t = new ‘ ( i n t , i n t ) [NN] ; f o r (NN i ) { r e s u l t [ i ] = ‘ ( a [ (NN) ( i − 1 ) ] , a [ (NN) ( i + 1 ) ] ) ; } r e t u r n new ‘ ( i n t , i n t ) [ [NN] ] ( r e s u l t ) ; }

D

10

FT

1

/∗ addition of 2 i n t e g e r s ∗/ public s t a t i c l o c a l i n t add ( ‘ ( i n t , i n t ) a ) { return a . 0 + a . 1 ; }

35 36 37 38 39

/∗ m u l t i p l i c a t i o n of 2 i n t e g e r s ∗/ public s t a t i c l o c a l i n t mult ( i n t [ [ 2 ] ] a ) { return a [ 0 ] ∗ a [ 1 ] ; }

40 41 42

/ ∗ d e t e r m i n e s i f i n t e g e r p i s a power o f i n t e g e r n ∗ / public s t a t i c boolean isPowerOf ( i n t p , i n t i ) {

2

a s s e r t i >= 2 ; while ( p >= i && p % i == 0 ) { p = p / i; } r e t u r n ( p == 1 ) ;

43 44 45 46 47

}

48 49

/ ∗ o u t p u t s a a r r a y o f t y p e T and l e n g t h NN ∗ / public s t a t i c void output ( T [NN] a ) { System . out . p r i n t l n ( a . c o n t e n t T o S t r i n g ( ) ) ; }

50 51 52 53 54

/ ∗ o u t p u t s a v a l u e a r r a y o f t y p e T and l e n g t h NN ∗ / public s t a t i c void output ( T [ [NN] ] a r r a y ) { System . out . p r i n t l n ( a r r a y . c o n t e n t T o S t r i n g ( ) ) ; }

55 56 57 58

FT

59

/ ∗ g e n e r a t e s a p s e u d o −random i n t e g e r v a l u e a r r a y o f l e n g t h NN ∗ / public g l o c a l s t a t i c i n t [ [NN] ] randomIntArray ( long seed ) { PseudoRandom rand = new PseudoRandom ( ) ; rand . s e t S e e d ( seed ) ; i n t [NN] r e s u l t = new i n t [NN] ; f o r (NN i ) { r e s u l t [ i ] = rand . n e x t I n t ( T o o l k i t .MAX) ; } r e t u r n new i n t [ [NN] ] ( r e s u l t ) ; }

60 61 62 63 64 65 66 67 68 69

RA

70

/ ∗ g e n e r a t e s a p s e u d o −random c o m p l e x v a l u e a r r a y o f l e n g t h NN ∗ / public g l o c a l s t a t i c complex [ [NN] ] randomComplexArray ( long seed ) { PseudoRandom rand = new PseudoRandom ( ) ; rand . s e t S e e d ( seed ) ; complex [NN] r e s u l t = new complex [NN] ; f o r (NN i ) { r e s u l t [ i ] = new complex ( ( f l o a t ) rand . nextDouble ( ) ∗ 1e −6f , ( f l o a t ) rand . nextDouble ( ) ∗ 1e −6f ) ; } r e t u r n new complex [ [NN] ] ( r e s u l t ) ; }

71 72 73 74 75 76 77

78 79 80 81

}

D

82

We also need a class as the sink of value array:

1 2 3



package common ;

public c l a s s ValueArraySink {

4 5 6

p r i v a t e i n t index ; p r i v a t e T [NN] r e s u l t ;

7 8 9 10 11

public ValueArraySink ( ) { t h i s . index = 0 ; t h i s . r e s u l t = new T [NN] ; }

12 13 14

/∗ records a value ∗/ public void dump( T t ) {

3

r e s u l t [ (NN) index ++] = t ;

15

}

16 17

/∗ returns the r e s u l t as a value array ∗/ public T [ [NN] ] g e t R e s u l t ( ) { r e t u r n new T [ [NN] ] ( r e s u l t ) ; }

18 19 20 21 22

24 25 26 27 28 29 30 31 32 33 34

FT

/ ∗ c o m p a r e s r e s u l t w i t h r e f e r e n c e and o u t p u t s t h e r e s u l t ∗ / public void compareWith ( T [NN] r e f e r e n c e ) { System . out . p r i n t ( T o o l k i t . RESULT ) ; T o o l k i t . output ( r e s u l t ) ; System . out . p r i n t ( T o o l k i t . REFERENCE) ; T o o l k i t . output ( r e f e r e n c e ) ; i f (new T [ [NN] ] ( r e s u l t ) == new T [ [NN] ] ( r e f e r e n c e ) ) { System . out . p r i n t l n ( T o o l k i t . TEST_PASS ) ; } else { System . e r r . p r i n t l n ( T o o l k i t . TEST_FAIL ) ; } }

23

35 36

}

as well as a sink of a single value:

package common ;

RA

1 2 3

public c l a s s ValueSink {

4

private T result ;

5 6

/∗ records the value ∗/ public void dump( T t ) { result = t ; }

7 8 9 10 11

/∗ returns the r e s u l t ∗/ public T g e t R e s u l t ( ) { return r e s u l t ; }

12 13 14

D

15 16

/ ∗ c o m p a r e s r e s u l t w i t h r e f e r e n c e and o u t p u t s t h e r e s u l t ∗ / public void compareWith ( T r e f e r e n c e ) { System . out . p r i n t l n ( T o o l k i t . RESULT + r e s u l t ) ; System . out . p r i n t l n ( T o o l k i t . REFERENCE + r e f e r e n c e ) ; i f ( r e s u l t == r e f e r e n c e ) { System . out . p r i n t l n ( T o o l k i t . TEST_PASS ) ; } else { System . e r r . p r i n t l n ( T o o l k i t . TEST_FAIL ) ; } }

17 18 19 20 21 22 23 24 25 26 27 28

}

Here is a value array generator, that generates a value at a time from a given value array:

4

1

package common ;

2 3

public f i n a l c l a s s ValueArrayGenerator {

4

p r i v a t e i n t index ; p r i v a t e T [ [NN] ] a r r a y ;

5 6 7

/ ∗ makes a s o u r c e t a s k f r o m t h e g i v e n v a l u e a r r a y ∗ / public s t a t i c t a s k IsoTask makeSource ( T [ [NN] ] a r r a y ) { r e t u r n t a s k (new ValueArrayGenerator ( a r r a y ) ) . next ; }

8 9 10 11 12

15 16 17 18

/∗ g e n e r a t e s the next value ∗/ public f i n a l l o c a l T next ( ) { i f ( index < NN. s i z e ) { r e t u r n a r r a y [ (NN) index + + ] ; } else { throw new StreamUnderflow ( ) ; } }

19 20 21 22 23 24 25 26

28

}

RA

27

FT

/∗ i n i t i a l i z e s the value array ∗/ public l o c a l ValueArrayGenerator ( T [ [NN] ] a r r a y ) { t h i s . index = 0 ; this . array = array ; }

13 14

A timer for performance measurements:



1

package common ;

2 3

public c l a s s Timer {

4

long timer , s t a r t T i m e ;

5 6

public void s t a r t ( ) { timer = 0 ; s t a r t T i m e = System . nanoTime ( ) ; }

7 8

D

9 10 11

public void s to p ( ) { t i m e r += System . nanoTime ( ) − s t a r t T i m e ; System . out . p r i n t l n ( " Time : " + ( t i m e r / 1 e9 ) + " s " ) ; }

12 13 14 15 16 17

}

Here are some commonly used permutations in stream computing: 1

package common ;

2 3

public c l a s s Permutations {

4 5 6

/ ∗ i d e n t i t y on a r r a y ∗ / public s t a t i c l o c a l T [ [NN] ] id ( T [ [NN] ] a r r a y ) {

5

return array ;

7 8

}

9 10 11

12 13 14 15

/ ∗ wraps up an I s o T a s k t a s k t o b e I s o T a s k ∗ / public t a s k s t a t i c l o c a l IsoTask s e r i a l i z e ( IsoTask F ) { r e t u r n ( TI # TI [ [NN] ] ) => F => (TO [ [NN] ] # TO) ; }

16

18

19 20 21 22

/ ∗ wraps up an I s o T a s k t a s k t o b e I s o T a s k ∗ / public t a s k s t a t i c l o c a l IsoTask p a r a l l e l i z e ( IsoTask F ) { r e t u r n ( TI [ [NN] ] # TI ) => F => (TO # TO [ [NN] ] ) ; }

FT

17

23

26 27 28 29 30 31 32 33 34 35 36

37 38 39 40 41 42 43 44 45 46 47

48 49 50 51 52 53 54 55 56 57 58 59 60

/∗ reord ers the array b e f o r e s p l i t t i n g ∗/ public s t a t i c l o c a l T [ [NN] ] s p l i t _ e v e n _ o d d ( T [ [NN] ] i np ut ) { T [NN] r e s u l t = new T [NN] ( input ) ; f i n a l i n t H = NN. s i z e / 2 ; f o r ( i n t i ) { r e s u l t [ (NN) ( 2 ∗ i ) ] = input [ i ] ; r e s u l t [ (NN) ( 2 ∗ i + 1 ) ] = input [ (NN) ( i + H) ] ; } r e t u r n new T [ [NN] ] ( r e s u l t ) ; }

RA

25

/∗ r e s t o r e s the array a f t e r s p l i t t i n g ∗/ public s t a t i c l o c a l T [ [NN] ] join_even_odd ( T [ [NN] ] i np ut ) { T [NN] r e s u l t = new T [NN] ( input ) ; f i n a l i n t H = NN. s i z e / 2 ; f o r ( i n t i ) { r e s u l t [ i ] = input [ (NN) ( 2 ∗ i ) ] ; r e s u l t [ (NN) ( i + H) ] = input [ (NN) ( 2 ∗ i + 1 ) ] ; } r e t u r n new T [ [NN] ] ( r e s u l t ) ; }

D

24

/∗ r e v e r s e the b i t s of each index ∗/ public s t a t i c l o c a l T [ [NN] ] b i t _ r e v e r s e ( T [ [NN] ] in pu t ) { T [NN] r e s u l t = new T [NN] ( input ) ; f i n a l i n t N = NN. s i z e ; f o r (NN i = 0 , j = 0 ; i < N − 1 ; i ++) { int k = N / 2; if ( i < j ) { T tmp = r e s u l t [ i ] ; result [ i ] = result [ j ] ; r e s u l t [ j ] = tmp ; } while ( k = ( complex t h a t ) { r e t u r n t h a t = 0 . 0 ? " + " : " " ) + imag + " i " ; }

8

}

109 110 111

}

The fork-join pattern lets control flow fork into multiple parallel flows that rejoin later [McCool et al., 2012]. A typical use is to create a group of tasks, applying the same operation in parallel. In Lime it can easily be implemented by using splitter and joiner: 1

/ ∗ f o r k −j o i n p a t t e r n ∗ /

2

5

6 7 8 9 10 11 12 13 14 15 16

/ ∗ PP : t h e number o f p a r a l l e l s u b t a s k s ∗ F : the t a s k to be p a r a l l e l i z e d ∗/ public t a s k s t a t i c l o c a l IsoTask f o r k j o i n ( IsoTask F ) { f i n a l i n t P = PP . s i z e ; r e t u r n ( TI # TI [ [ P ] ] ) / / TI [ [ P ] ] => t a s k s p l i t TI [ [ P ] ] / / ‘ ( TI , TI , . . . , TI ) => t a s k [ ( P ) F ] / / ‘ (TO, TO, . . . , TO) => t a s k j o i n TO [ [ P ] ] / / TO [ [ P ] ] => (TO [ [ P ] ] # TO) ; }

FT

3 4

17

20 21 22 23

24 25 26 27 28 29 30 31 32 33 34

/ ∗ f o r k −j o i n p a t t e r n t h a t m a i n t a i n s t h e o r d e r ∗ /

RA

19

/ ∗ SS : t h e s i z e o f s u b t a s k s ∗ PP : t h e number o f p a r a l l e l s u b t a s k s ∗ F : the t a s k to be p a r a l l e l i z e d ∗/ public t a s k s t a t i c l o c a l IsoTask f o r k j o i n O r d e r e d ( IsoTask F ) { f i n a l i n t P = PP . s i z e ; r e t u r n ( TI # TI [ [ P ] [ SS ] ] ) / / TI [ [ P ] [ SS ] ] => t a s k s p l i t TI [ [ P ] [ SS ] ] / / ‘ ( TI [ SS ] , TI [ SS ] , . . . , TI [ SS ] ) => t a s k [ ( P ) F ] / / ‘ (TO[ SS ] , TO[ SS ] , . . . , TO[ SS ] ) => t a s k j o i n TO [ [ P ] [ SS ] ] / / TO [ [ P ] [ SS ] ] => (TO [ [ P ] [ SS ] ] # TO) ; }

D

18

U SED IN: src/common/GenericPatterns.lime on page 1

One test case is to create 4 parallel tasks that all increase an integer by 1: 1

package t e s t c a s e s . f o r k j o i n ;

2 3 4

import common . ∗ ; import lime . u t i l . Tasks ;

5 6

public c l a s s I n c {

7

9

/ ∗ a p p l y t a s k i n c t o an i n t e g e r a r r a y ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( I n c . c l a s s . getName ( ) ) ; final int N = 8 , P = 4; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ;

8 9 10 11 12 13

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ G e n e r i c P a t t e r n s . < i n t , i n t , P> f o r k j o i n ( t a s k T o o l k i t . i n c ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

14 15 16 17 18 19 20 21 22 23

25 26 27 28 29

FT

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; f o r ( i n t i ) { reference [ i ] = Toolkit . inc ( reference [ i ] ) ; } s i n k . compareWith ( r e f e r e n c e ) ;

24

}

30 31 32

}

RA

A variation of fork-join pattern would be parallel generator pattern, which generates values in parallel:

2 3 4 5

6 7 8 9 10 11

/∗ p a r a l l e l generator pattern ∗/

/ ∗ PP : t h e number o f p a r a l l e l g e n e r a t o r s ∗ gen : t h e g e n e r a t o r t a s k s t o b e p a r a l l e l i z e d ∗ / public t a s k s t a t i c l o c a l IsoTask p a r a l l e l G e n e r a t o r ( IsoTask [ [ PP ] ] gens ) { r e t u r n t a s k [ gens ] / / ‘(T , T , . . . , T) => t a s k j o i n T [ [ PP ] ] // T[[P]] => ( T [ [ PP ] ] # T ) ; }

D

1

U SED IN: src/common/GenericPatterns.lime on page 1

We use random integer value arrays to test:

1



package t e s t c a s e s . pargen ;

2 3 4

import common . ∗ ; import lime . u t i l . Tasks ;

5 6

public c l a s s Weave {

7 8

s t a t i c f i n a l i n t N = 4 , P = 4 , TOTAL = N ∗ P ;

9 10 11

/∗ i n i t i a l i z e s the g e n e r a t o r s as r e p e a t a b l e e x p r e s s i o n s ∗/ s t a t i c l o c a l IsoTask [ [NN] ] i n i t ( ) {

10

f i n a l i n t n = NN. s i z e ; IsoTask [ [ 1 ] ] t = { ValueArrayGenerator < i n t , N> . makeSource ( T o o l k i t . randomIntArray ( n ) ) } ; i f ( n == 1 ) { r e t u r n new IsoTask [ [NN] ] ( t ) ; } else { f i n a l i n t SUB = n − 1 ; r e t u r n new IsoTask [ [NN] ] ( Weave. i n i t ( ) + t ) ; }

12 13

14 15 16 17 18 19

}

20 21

/∗ weaves N i n t e g e r value a r r a y s ∗/ public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( Weave . c l a s s . getName ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; i n t [ P ] [N] intN = new i n t [ P ] [N] ; f o r ( i n t

i ) { intN [ i ] = new i n t [N] ( T o o l k i t . randomIntArray ( i + 1 ) ) ; T o o l k i t . output ( intN [ i ] ) ; }

22 23 24 25 26

FT

27 28 29 30 31

33 34 35 36 37 38 39 40

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [TOTAL] r e f e r e n c e = new i n t [TOTAL ] ; f o r ( i n t i ) { f o r ( i n t

j ) { r e f e r e n c e [ i ∗ P + j ] = intN [ j ] [ i ] ; } } s i n k . compareWith ( r e f e r e n c e ) ;

41 42 43 44 45 46 47 48

50

}

D

}

49

51

RA

/ / computes th e r e s u l t ValueArraySink < i n t , TOTAL> s i n k = new ValueArraySink < i n t , TOTAL> ( ) ; var t = ( [ G e n e r i c P a t t e r n s . < i n t , P> p a r a l l e l G e n e r a t o r ( Weave.

i n i t ( ) ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

32

In a map pattern, a function is applied to all elements of a collection, usually producing a new collection with the same shape as the input, and the elementary function must be pure (without side effects) [McCool et al., 2012].

1 2 3 4 5

6 7



/ ∗ map p a t t e r n ∗ /

/∗ b a s i c version , i d e n t i c a l to input ∗ F : t h e map f i l t e r ∗ / public t a s k s t a t i c l o c a l IsoTask map( IsoTask F ) { return F ; }

8 9 10 11

/∗ p a r a l l e l version ∗ PP : t h e number o f p a r a l l e l s u b t a s k s ∗ F : t h e map f i l t e r ∗ /

11

12

13 14

public t a s k s t a t i c l o c a l IsoTask m a p P a r a l l e l ( IsoTask F ) { r e t u r n G e n e r i c P a t t e r n s . < TI , TO, PP> f o r k j o i n ( F ) ; }

15 16 17 18 19

20 21 22 23 24 25

/∗ array version ∗ NN: i n p u t a r r a y l e n g t h ∗ F : t h e map f i l t e r ∗ / public t a s k s t a t i c l o c a l IsoTask mapArray ( IsoTask F ) { r e t u r n ( TI [ [NN] ] # TI ) / / TI => F / / TO => (TO # TO [ [NN] ] ) ; }

29 30 31

32 33 34 35 36 37

/ ∗ p a r a l l e l v e r s i o n on v a l u e a r r a y ∗ NN: i n p u t a r r a y l e n g t h ∗ PP : t h e number o f p a r a l l e l s u b t a s k s ∗ F : t h e map f i l t e r ∗ / public t a s k s t a t i c l o c a l IsoTask mapArrayParallel ( IsoTask F ) { r e t u r n ( TI [ [NN] ] # TI ) / / TI => G e n e r i c P a t t e r n s . < TI , TO, PP> f o r k j o i n ( F ) / / TO => (TO # TO [ [NN] ] ) ; }

RA

27 28

FT

26

U SED IN: src/common/GenericPatterns.lime on page 1

The following program tests MapParallel:



2 3 4 5 6 7 8 9 10 11 12 13 14

package t e s t c a s e s . map ;

import common . ∗ ; import lime . u t i l . Tasks ; public c l a s s I n c {

D

1

/ ∗ a p p l y s t a s k i n c t o an i n t e g e r a r r a y ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( I n c . c l a s s . getName ( ) ) ; final int N = 8 , P = 4; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

15 16 17 18 19 20 21 22 23 24

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ G e n e r i c P a t t e r n s . < i n t , i n t , P> m a p P a r a l l e l ( t a s k T o o l k i t . i n c ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

12

25

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; f o r ( i n t i ) { reference [ i ] = Toolkit . inc ( reference [ i ] ) ; } s i n k . compareWith ( r e f e r e n c e ) ;

26 27 28 29 30 31

}

32 33 34

}

A recurrence is like a map but where elements can use the outputs of adjacent elements as inputs [McCool et al., 2012]. Here we show how iteration can be done in this style. 1

/∗ recurrence pattern

4 5

6 7 8 9 10 11 12 13 14 15

/ ∗ NN: number o f i t e r a t i o n s ∗ F : the i t e r a t i v e operation ∗/ public t a s k s t a t i c l o c a l IsoTask r e c u r r e n c e ( IsoTask F ) { i f (NN. s i z e > 1 ) { f i n a l i n t SUB = NN. s i z e − 1 ; return F // T => G e n e r i c P a t t e r n s . r e c u r r e n c e ( F ) ; } else { return F ; } }

RA

3

FT

2

U SED IN: src/common/GenericPatterns.lime on page 1

Here is a test case that increases integer values multiple times:



2 3 4 5 6 7 8 9 10 11 12 13 14

package t e s t c a s e s . r e c u r r e n c e ; import common . ∗ ; import lime . u t i l . Tasks ;

D

1

public c l a s s I n c {

/ ∗ a p p l y s t a s k i n c t o an i n t e g e r v a l u e a r r a y m u l t i p l e t i m e s ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( I n c . c l a s s . getName ( ) ) ; final int N = 8 , R = 4; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

15 16 17 18 19 20 21

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ G e n e r i c P a t t e r n s . < i n t , R> r e c u r r e n c e ( t a s k T o o l k i t . i n c ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ;

13

timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

22 23 24 25

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; f o r ( i n t i ) { f o r ( i n t j ) { reference [ i ] = Toolkit . inc ( reference [ i ] ) ; } } s i n k . compareWith ( r e f e r e n c e ) ;

26 27 28 29 30 31 32 33

}

34 35 36

}

FT

The stencil pattern is a generalization of the map pattern where the elemental function can access not only a single element in an input collection but also a set of “neighbors” [McCool et al., 2012]. 1

/∗ s t e n c i l pattern ∗/

2

5 6

7 8 9 10 11 12 13

14 15

/∗ b a s ic version ∗ CALC : c a l c u l a t e s t h e s t r e a m o f new v a l u e s ∗ TI : s u p p o s e d t o b e ‘ (TO, TO, . . . , TO) ∗ / public t a s k s t a t i c l o c a l IsoTask s t e n c i l ( IsoTask CALC) { r e t u r n G e n e r i c P a t t e r n s . < TI , TO>map(CALC) ; }

RA

3 4

/∗ p a r a l l e l version ∗ CALC : c a l c u l a t e s t h e s t r e a m o f new v a l u e s ∗ TI : s u p p o s e d t o b e ‘ (TO, TO, . . . , TO) ∗ / public t a s k s t a t i c l o c a l IsoTask s t e n c i l P a r a l l e l ( IsoTask CALC) { r e t u r n G e n e r i c P a t t e r n s . < TI , TO, PP> m a p P a r a l l e l (CALC) ; } U SED IN: src/common/GenericPatterns.lime on page 1

D

The following test case shows how to use stencil pattern to calculate the sum of the two neighbors of each element in an integer value array:

1 2 3 4

package t e s t c a s e s . s t e n c i l ; import common . ∗ ; import lime . u t i l . Tasks ;

5 6

public c l a s s SumNeighbors {

7 8 9 10 11 12 13 14

/ ∗ c a l c u l a t e s t h e sum o f t h e two n e i g h b o r s o f e a c h e l e m e n t i n an i n t e g e r v a l u e a r r a y ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( SumNeighbors . c l a s s . getName ( ) ) ; final int N = 8 , P = 4; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

14

15

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( # i n t [ [ 8 ] ] ) => t a s k T o o l k i t . genNeighbors => ( # ‘ ( i n t , i n t ) ) => ( [ G e n e r i c P a t t e r n s . < ‘ ( i n t , i n t ) , i n t , P> s t e n c i l P a r a l l e l ( t a s k T o o l k i t . add ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

16 17 18 19 20 21 22 23 24 25 26 27 28

30 31 32 33 34 35 36

FT

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; int s = 0; f o r ( i n t i ) { s = T o o l k i t . add ( ‘ ( intN [ ( i n t ) ( i − 1 ) ] , intN [ ( i n t ) ( i + 1 ) ] ) ) ; reference [ i ] = s ; } s i n k . compareWith ( r e f e r e n c e ) ;

29

}

37 38 39

}

RA

A reduction combines all the elements in a collection into a single element using an associative combiner function. Because the combiner function is associative, many orderings are possible [McCool et al., 2012].

2 3 4 5 6

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

/∗ reduction pattern ∗/

/ ∗ F : r e d u c t i o n on PP e l e m e n t s ∗ NN: t h e s i z e o f t h e v a l u e a r r a y PP : t h e number o f sub −p r o b l e m s i n e a c h s t e p ∗ / public t a s k s t a t i c l o c a l IsoTask r e d u c t i o n ( IsoTask F ) { f i n a l i n t N = NN. s i z e ; f i n a l i n t P = PP . s i z e ; a s s e r t N >= P && T o o l k i t . isPowerOf (N, P ) ; f i n a l i n t SUB = N / P ; i f (N > P ) { IsoTask s u b r e d u c t i o n = G e n e r i c P a t t e r n s . r e d u c t i o n ( F ) ; r e t u r n G e n e r i c P a t t e r n s . f o r k j o i n ( s u b r e d u c t i o n ) // T => ( T # T [ [ PP ] ] ) / / T [ [ PP ] ] => F ; } else { / / a p p l y F when o n l y PP e l e m e n t s r e t u r n ( T # T [ [ PP ] ] ) => F ; } }

D

1

U SED IN: src/common/GenericPatterns.lime on page 1

Here is a test case of computing the product of an integer value array: 15

1

package t e s t c a s e s . r e d u c t i o n ;

2 3 4

import common . ∗ ; import lime . u t i l . Tasks ;

5 6

public c l a s s Product {

7

/ ∗ c a l c u l a t e s t h e p r o d u c t o f an i n t e g e r v a l u e a r r a y ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( Product . c l a s s . getName ( ) ) ; final int N = 8; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

8 9 10 11 12 13 14 15

/ / computes th e r e s u l t ValueSink < i n t > s i n k = new ValueSink < i n t > ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ G e n e r i c P a t t e r n s . < i n t , N, 2> r e d u c t i o n ( t a s k T o o l k i t . mult ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

FT

16 17 18 19 20 21 22 23 24 25

27 28 29 30 31 32 33 34

}

35 36 37

}

RA

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s int reference = 1; i n t [ 2 ] a = new i n t [ 2 ] ; f o r ( i n t i ) { a [0] = reference ; a [ 1 ] = intN [ i ] ; r e f e r e n c e = T o o l k i t . mult (new i n t [ [ 2 ] ] ( a ) ) ; } s i n k . compareWith ( r e f e r e n c e ) ;

26

1 2 3 4

5 6 7 8 9

10 11

D

Scan computes all partial reductions of a collection. In other words, for every output position, a reduction of the input up to that point is computed [McCool et al., 2012]. Note that here the basic operation must be on two elements, otherwise not every element has a partial reduction up to it.

/∗ scan p a t t e r n ∗/

/ ∗ TN : a g e n e r i c n u m e r i c t y p e w i t h + o p e r a t i o n , i s n e c e s s a r y f o r method s c a n _ m e r g e ∗ / public t a s k s t a t i c l o c a l IsoTask scan ( ) { f i n a l i n t N = NN. s i z e ; a s s e r t N >= 2 && T o o l k i t . isPowerOf (N, 2 ) ; i f (N > 2 ) { final int H = N / 2; r e t u r n G e n e r i c P a t t e r n s . f o r k j o i n O r d e r e d ( Permutations . p a r a l l e l i z e ( G e n e r i c P a t t e r n s . scan ( ) ) ) / / TN => Permutations . s e r i a l i z e ( t a s k G e n e r i c P a t t e r n s . scan_merge ) ;

16

} else { r e t u r n Permutations . s e r i a l i z e ( t a s k G e n e r i c P a t t e r n s . scan_merge ) ; }

12 13 14

15 16

}

17

19

20 21 22 23 24 25 26 27 28 29 30

/ ∗ m e r g e s t h e two s c a n n e d p a r t s ∗ / public s t a t i c l o c a l TN[ [NN] ] scan_merge (TN[ [NN ] ] in put ) { TN[NN] r e s u l t = new TN[NN] ( input ) ; f i n a l i n t N = NN. s i z e ; final int H = N / 2; / / t h e s e c o n d h a l f i n c r e a s e d by i [m − 1 ] / / c o u l d n o t f i n d a e q u i v a l e n t o p e r a t i o n on TI w i t h a f i l t e r a s p a r a m e t e r , / / s o TN was i n t r o d u c e d f o r (NN i = (NN) H; i > 0 ; i ++) { r e s u l t [ i ] += r e s u l t [ (NN) (H − 1 ) ] ; } r e t u r n new TN[ [NN] ] ( r e s u l t ) ; } U SED IN: src/common/GenericPatterns.lime on page 1

The following test case computes scan of summation:

FT

18



3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

package t e s t c a s e s . scan ;

RA

2

import common . ∗ ; import lime . u t i l . Tasks ; public c l a s s Sum {

/ ∗ c a l c u l a t e s s c a n o f summation ∗ / public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n (Sum . c l a s s . getName ( ) ) ; final int N = 8; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

D

1

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ G e n e r i c P a t t e r n s . < i n t , 8> scan ( ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

25 26 27 28 29 30 31 32

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; int s = 0; f o r ( i n t i ) { s = T o o l k i t . add ( ‘ ( s , r e f e r e n c e [ i ] ) ) ; reference [ i ] = s ; }

17

s i n k . compareWith ( r e f e r e n c e ) ;

33

}

34 35 36

}

Divide and conquer differently pattern divides the problem into several sub-problems that potentially have different solutions, solve them in parallel, and merge the outputs into the final result. 1

/ ∗ d i v i d e and c o n q u e r d i f f e r e n t l y p a t t e r n ∗ /

2

5 6

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

/ ∗ NN: t h e s i z e o f t h e v a l u e a r r a y ∗ SS : t h e s i z e o f sub −p r o b l e m s ∗ PP : t h e number o f sub −p r o b l e m s i n e a c h s t e p ∗ / public t a s k s t a t i c l o c a l IsoTask dncd ( IsoTask [ [ PP ] ] subs , IsoTask merge ) { f i n a l i n t N = NN. s i z e ; f i n a l i n t P = PP . s i z e ; final int S = N / P ; a s s e r t N == S ∗ P && P >= 2 && T o o l k i t . isPowerOf (N, P ) ; i f (N > P ) { r e t u r n ( T # T [ [ PP ] [ SS ] ] ) / / T [ [ PP ] [ SS ] ] => t a s k s p l i t T [ [ PP ] [ SS ] ] / / ‘ ( T [ SS ] , T [ SS ] , . . . , T [ SS ] ) => t a s k [ subs ] / / ‘ ( T [ SS ] , T [ SS ] , . . . , T [ SS ] ) => t a s k j o i n T [ [ PP ] [ SS ] ] / / T [ [ PP ] [ SS ] ] => ( T [ [ PP ] [ SS ] ] # T ) // T => merge ; } else { r e t u r n merge ; } }

FT

4

RA

3

28

31 32

/ ∗ by d e f a u l t PP = 2 ∗ / public t a s k s t a t i c l o c a l IsoTask dncdD ( IsoTask [ [ 2 ] ] subs , IsoTask merge ) { r e t u r n G e n e r i c P a t t e r n s . dncd ( subs , merge ) ; }

D

29 30

U SED IN: src/common/GenericPatterns.lime on page 1

An application would be bitonic sort (http://en.wikipedia.org/wiki/Bitonic_sorter), where the first half and the second half have different values of parameter upsort (part of the code is taken from Lime sample projects). 1

package examples . dncd ;

2 3 4

import common . ∗ ; import lime . u t i l . Tasks ;

5 6

public c l a s s B i t o n i c S o r t {

7 8

/∗ ∗

18

9 10 11 12 13 14 15 16 17

∗ Compares t h e two i n p u t k e y s and e x c h a n g e s t h e i r o r d e r i f t h e y a r e ∗ not s o r t e d . ∗ ∗ u p s o r t d e t e r m i n e s i f t h e s o r t i s n o n d e c r e a s i n g (UP) o r ∗ n o n i n c r e a s i n g (DOWN) . ’ t r u e ’ i n d i c a t e s UP s o r t and ’ f a l s e ’ ∗ i n d i c a t e s DOWN s o r t . ∗/ s t a t i c l o c a l ‘ ( i n t , i n t ) compareExchange ( ‘ ( i n t , i n t ) k , boolean u ps o r t ) { i n t mink , maxk ; i f ( k . 0 k1 ∗ / mink = k . 1 ; maxk = k . 0 ; }

19 20 21 22 23 24 25 26 27

i f ( u p so r t ) { r e t u r n ‘ ( mink , maxk ) ; / ∗ UP s o r t ∗ / } else { r e t u r n ‘ ( maxk , mink ) ; / ∗ DOWN s o r t ∗ / }

28 29 30 31 32 33 34

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

RA

37

}

/∗ ∗ ∗ P a r t i t i o n s t h e i n p u t b i t o n i c s e q u e n c e o f l e n g t h L i n t o two b i t o n i c ∗ s e q u e n c e s o f l e n g t h L / 2 , w i t h a l l numbers i n t h e f i r s t s e q u e n c e b i t o n i c _ c o m p a r e ( boolean u ps o r t ) { final int H = L . size / 2; var xchange = ( i n t # ‘ ( i n t , i n t ) ) => t a s k compareExchange ( ‘ ( i n t , i n t ) , u p so r t ) => ( # i n t ) ; / ∗ Each c o m p a r e E x c h a n g e e x a m i n e s k e y s t h a t a r e L / 2 e l e m e n t s a p a r t ∗ / r e t u r n ( i n t # i n t [ [H] ] ) => t a s k s p l i t i n t [ [H] ] => t a s k [ (H) xchange ] => t a s k j o i n i n t [ [H] ] => ( i n t [ [H] ] # i n t ) ; }

D

35 36

FT

18

60 61 62 63 64 65 66 67

s t a t i c l o c a l IsoTask < i n t , i n t > b i t o n i c _ l e v e l ( boolean u ps o r t ) { f i n a l i n t LL = L . s i z e ; final int H = L . size / 2; i f ( LL > 2 ) { r e t u r n B i t o n i c S o r t . b i t o n i c _ c o m p a r e ( u ps o rt ) => B i t o n i c S o r t . b i t o n i c _ l e v e l ( u ps o r t ) ; } else {

19

r e t u r n B i t o n i c S o r t . b i t o n i c _ c o m p a r e ( up s o rt ) ;

68

}

69

}

70 71

t a s k s t a t i c l o c a l IsoTask < i n t , i n t > c o n s t r u c t ( boolean u ps o r t ) { f i n a l i n t N = NN. s i z e ; i f (N > 2 ) { final int H = N / 2; IsoTask < i n t [ [H] ] , i n t [ [H] ] > [ 2 ] subs = new IsoTask < i n t [ [H] ] , i n t [ [H] ] > [ 2 ] ; subs [ 0 ] = Permutations . < i n t , i n t , H> p a r a l l e l i z e ( B i t o n i c S o r t . c o n s t r u c t ( u p s o r t ) ); subs [ 1 ] = Permutations . < i n t , i n t , H> p a r a l l e l i z e ( B i t o n i c S o r t . c o n s t r u c t ( ! u p s o r t )); IsoTask < i n t [ [H] ] , i n t [ [H] ] > [ [ 2 ] ] s u b t a s k s = new IsoTask < i n t [ [H] ] , i n t [ [H ] ] > [ [ 2 ] ] ( subs ) ; r e t u r n G e n e r i c P a t t e r n s . < i n t , NN, H, 2>dncd ( subtasks , B i t o n i c S o r t . b i t o n i c _ l e v e l ( u p so r t ) ) ; } else { r e t u r n B i t o n i c S o r t . b i t o n i c _ l e v e l ( up s o rt ) ; } }

72 73 74 75 76 77

78

79

FT

80

81 82 83 84 85

public s t a t i c void main ( S t r i n g [ ] a r g s ) { final int N = 8; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

86 87 88 89 90

RA

91

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ B i t o n i c S o r t . c o n s t r u c t ( t r u e ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

92 93 94 95 96 97 98 99 100 101

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; reference . sort ( ) ; s i n k . compareWith ( r e f e r e n c e ) ;

102 103

}

106 107 108

D

104 105

}

Divide and conquer pattern is the special case of divide and conquer differently pattern, where all the sub-solutions are the same. 1

/ ∗ d i v i d e and c o n q u e r p a t t e r n ∗ /

2 3 4 5 6

7 8

/ ∗ NN: t h e s i z e o f t h e v a l u e a r r a y ∗ SS : t h e s i z e o f sub −p r o b l e m s ∗ PP : t h e number o f sub −p r o b l e m s i n e a c h s t e p ∗ / public t a s k s t a t i c l o c a l IsoTask dnc ( IsoTask sub , IsoTask merge ) { f i n a l i n t N = NN. s i z e ; f i n a l i n t P = PP . s i z e ;

20

final int S = N / P ; a s s e r t N == S ∗ P && P >= 2 && T o o l k i t . isPowerOf (N, P ) ; IsoTask [ PP ] subs = new IsoTask [ PP ] ( ) ; f o r ( PP i ) { subs [ i ] = Permutations . p a r a l l e l i z e ( sub ) ; } IsoTask [ [ PP ] ] s u b t a s k s = new IsoTask [ [ PP ] ] ( subs ) ; i f (N > P ) { r e t u r n G e n e r i c P a t t e r n s . dncd ( subtasks , merge ) ; } else { r e t u r n merge ; }

9 10 11 12 13 14 15

16 17 18 19 20 21 22

}

23

26 27

/ ∗ by d e f a u l t PP = 2 ∗ / public t a s k s t a t i c l o c a l IsoTask dncD ( IsoTask sub , IsoTask merge ) { r e t u r n G e n e r i c P a t t e r n s . dnc ( sub , merge ) ; } U SED IN: src/common/GenericPatterns.lime on page 1

FT

24 25

One applicaiton would be merge sort (http://en.wikipedia.org/wiki/Merge_sort):

3 4 5 6 7 8 9 10

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

package examples . dnc ;

RA

2

import common . ∗ ; import lime . u t i l . Tasks ;

public c l a s s MergeSort {

t a s k s t a t i c l o c a l IsoTask < i n t , i n t > c o n s t r u c t ( ) { f i n a l i n t N = NN. s i z e ; IsoTask < i n t , i n t > merge = Permutations . < i n t , i n t , NN> s e r i a l i z e ( t a s k MergeSort . merge ) ; i f (N > 2 ) { final int H = N / 2; r e t u r n G e n e r i c P a t t e r n s . < i n t , NN, H>dncD ( MergeSort . c o n s t r u c t ( ) , merge ) ; } else { r e t u r n merge ; } }

D

1

public s t a t i c l o c a l i n t [ [NN] ] merge ( i n t [ [NN] ] input ) { NN i = 0 ; NN H = (NN) (NN. s i z e / 2 ) ; NN j = H; NN c = 0 ; NN r = 0 ; f i n a l r e s u l t = new i n t [NN] ;

26 27 28 29 30 31

while ( t r u e ) { i n t l v = input [ i ] ; i n t uv = input [ j ] ; i f ( l v < uv ) { r e s u l t [ c ++] = l v ;

21

i ++; i f ( i == H) { r = j; break ; }

32 33 34 35 36

} else { r e s u l t [ c ++] = uv ; j ++; i f ( j == 0 ) { r = i; break ; / / NOTE: "1n" d o e s n ’ t work } }

37 38 39 40 41 42 43 44 45

}

46

48

//

NN r = i == 0 ? j : i ; while ( c ! = 0 ) { r e s u l t [ c ++] = input [ r + + ] ; }

49 50 51 52

r e t u r n new i n t [ [NN] ] ( r e s u l t ) ;

53

}

54 55

public s t a t i c void main ( S t r i n g [ ] a r g s ) { final int N = 8; i n t [ [N] ] intN = T o o l k i t . randomIntArray ( System . nanoTime ( ) ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; T o o l k i t . < i n t , N>output ( intN ) ;

56 57 58

RA

59 60 61

/ / computes th e r e s u l t ValueArraySink < i n t , N> s i n k = new ValueArraySink < i n t , N> ( ) ; var t = Tasks . s o u r c e ( intN ) => ( [ MergeSort . c o n s t r u c t ( ) ] ) => t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

62 63 64 65 66 67 68 69 70 71

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s i n t [N] r e f e r e n c e = new i n t [N] ( intN ) ; reference . sort ( ) ; s i n k . compareWith ( r e f e r e n c e ) ;

73 74 75

D

72

}

76 77 78

FT

47

}

Here is an example of fast Fourier transform (http://en.wikipedia.org/wiki/Fast_Fourier_transform): 1

package examples . dnc ;

2 3 4 5

import common . ∗ ; import lime . u t i l . PseudoRandom ; import lime . u t i l . Tasks ;

6 7

public c l a s s FFT {

8

22

9

public s t a t i c f i n a l long SEED = 1 0 1 0 1 0 1 0 ;

10 11 12 13

14 15 16 17 18 19 20

t a s k s t a t i c l o c a l IsoTask c o n s t r u c t ( ) { f i n a l i n t N = NN. s i z e ; IsoTask merge = Permutations . < complex , complex , NN> s e r i a l i z e ( t a s k FFT . merge ) ; i f (N > 2 ) { final int H = N / 2; r e t u r n G e n e r i c P a t t e r n s . < complex , NN, H>dncD ( FFT . c o n s t r u c t ( ) , merge ) ; } else { r e t u r n merge ; } }

21

23 24 25 26

public s t a t i c l o c a l complex [ [NN] ] merge ( complex [ [NN] ] input ) { f i n a l i n t N = NN. s i z e ; final int H = N / 2; f i n a l r e s u l t = new complex [NN] ; double t h e t a = − 2.0 f ∗ ( double ) Math . PI / ( ( double ) N) ;

FT

22

27

complex w = new complex ( 1 , 0 ) ; complex ww = new complex ( Math . cos ( t h e t a ) , Math . s i n ( t h e t a ) ) ;

28 29 30

32 33 34 35 36

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

54

}

public s t a t i c void s i n k ( complex c ) { System . out . p r i n t l n ( c ) ; }

public s t a t i c void main ( S t r i n g [ ] a r g s ) { final int N = 4; complex [ [N] ] complexN = T o o l k i t . randomComplexArray ( SEED ) ; System . out . p r i n t ( T o o l k i t . INPUT ) ; / / computes th e r e s u l t ValueArraySink s i n k = new ValueArraySink ( ) ; complex [ [N] ] complexI = Permutations . < complex , N> b i t _ r e v e r s e ( complexN ) ; T o o l k i t . < complex , N>output ( complexI ) ; var t = Tasks . s o u r c e ( complexN ) => Permutations . < complex , complex , N> s e r i a l i z e ( t a s k Permutations . < complex , N> bit_reverse ) => ( [ FFT . c o n s t r u c t ( ) ] )

D

37

RA

f o r (NN i = 0 ; i < H; i ++) { r e s u l t [ i ] = input [ i ] + w ∗ input [ (NN) ( i + H) ] ; r e s u l t [ (NN) ( i + H) ] = input [ i ] − w ∗ input [ (NN) ( i + H) ] ; w = w ∗ ww; } r e t u r n new complex [ [NN] ] ( r e s u l t ) ;

31

55 56 57 58 59 60

=> t a s k s i n k . dump ; Timer t i m e r = new Timer ( ) ; timer . s t a r t ( ) ; t . finish () ; t i m e r . s to p ( ) ;

61 62 63 64

/ / c o m p u t e s t h e r e f e r e n c e and c o m p a r e s complex [N] r e f e r e n c e = new complex [N] ( complexN ) , temp = new complex [N] ; r e f e r e n c e = new complex [N] ( Permutations . < complex , N> b i t _ r e v e r s e (new complex [ [N] ] ( reference ) ) ) ;

23

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

FT

f o r ( i n t d = 1 ; d < N; d ∗= 2 ) { double t h e t a = − 2.0 f ∗ ( double ) Math . PI / ( ( double ) d ∗ 2 ) ; complex w = new complex ( 1 , 0 ) ; complex ww = new complex ( Math . cos ( t h e t a ) , Math . s i n ( t h e t a ) ) ; int c = 0; complex temp1 , temp2 ; f o r ( i n t i = 0 ; i < N; i ++) { temp1 = r e f e r e n c e [ ( i n t ) i ] + w ∗ r e f e r e n c e [ ( i n t ) ( i + d ) ] ; temp2 = r e f e r e n c e [ ( i n t ) i ] − w ∗ r e f e r e n c e [ ( i n t ) ( i + d ) ] ; temp [ ( i n t ) i ] = temp1 ; temp [ ( i n t ) ( i + d ) ] = temp2 ; w = w ∗ ww; c ++; i f ( c == d ) { i += d ; w = new complex ( 1 , 0 ) ; c = 0; } } r e f e r e n c e = temp ; } s i n k . compareWith ( r e f e r e n c e ) ;

65

}

87 88

References

RA

}

Auerbach, J., D. F. Bacon, P. Cheng, and R. Rabbah (2010). Lime: The liquid metal programming language – language reference manual. Technical report, IBM Research Division. Gamma, E., R. Helm, R. Johnson, and J. M. Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional. Gosling, J., B. Joy, G. Steele, and G. Bracha (2005, May). The Java language specification, thrid edition. http: //docs.oracle.com/javase/specs/jls/se5.0/jls3.pdf. McCool, M., A. D. Robison, and J. Reinders (2012). Structured parallel programming: patterns for efficient computation. Amsterdam: Elsevier/Morgan Kaufmann.

D

89

24