1 Cilk: Φιλοσοφία και Χρήση Παράλληλα Συστήματα Επεξεργασίας 9ο εξάμηνο ΣΗΜΜΥ ακ. έτος 2012-2013 http://www.cslab.ece.ntua.gr/courses/pps Εργαστήριο Υπολογιστικών Συστημάτων ΕΜΠ Δεκέμβριος 2012
Cilk 2 Επέκταση της C με λίγα keywords Επικεφαλής της ομάδας ο καθ. Charles E. Leiserson (MIT) Ισχυρό θεωρητικό υπόβαθρο φράγματα για χρόνο και χώρο παράληλης εκτέλεσης έμφαση στην αποδοτική χρονοδρομολόγηση Ιστορία - 1994: Πρώτη έκδοση (Cilk-1) - 1998: Implementation of the Cilk-5 Multithreaded Language paper - 2006: Cilk Arts - 2008: Cilk++ 1.0-2009: Αγορά Cilk arts από Intel - 2010: Release of Intel Cilk Plus - 2011: Cilk Plus for GCC4.7
3 Cilk keywords - Basic cilk: Ορισμός συναρτήσεων που ορίζουν Cilk διεργασίες spawn: Δημιουργία Cilk διεργασίας Ο πατέρας και το παιδί μπορούν να εκτελεστούν παράλληλα sync: Αναμονή ολοκλήρωσης Cilk διεργασιών Τα αποτελέσματα των παιδιών είναι έτοιμα cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y);
4 Ο γράφος διεργασιών αναδιπλώνεται δυναμικά (στο χρόνο εκτέλεσης) cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y);.
4 Ο γράφος διεργασιών αναδιπλώνεται δυναμικά (στο χρόνο εκτέλεσης) cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y); f(4).
4 Ο γράφος διεργασιών αναδιπλώνεται δυναμικά (στο χρόνο εκτέλεσης) cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y); f(4) f(3). f(2)
4 Ο γράφος διεργασιών αναδιπλώνεται δυναμικά (στο χρόνο εκτέλεσης) cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y); f(4) f(3). f(2) f(2) f(1) f(1) f(0) f(1) f(0)
4 Ο γράφος διεργασιών αναδιπλώνεται δυναμικά (στο χρόνο εκτέλεσης) cilk int fib(int n) { if (n < 2) return (n); x = spawn fib(n - 1); y = spawn fib(n - 2); sync; return (x + y); f(4) f(3). f(2) f(2) f(1) f(1) f(0) f(1) f(0)
5 Παράδειγμα: πρόσθεση Μετατροπή αλγορίθμου συσσωρευτή σε διαίρε και βασίλευε N i=0 A i add(a,n){ res = 0; for (i=0; i<n; i++) res += A[i]; return res;
5 Παράδειγμα: πρόσθεση Μετατροπή αλγορίθμου συσσωρευτή σε διαίρε και βασίλευε N i=0 A i add(a,n){ res = 0; for (i=0; i<n; i++) res += A[i]; return res; add_r(a, n){ if (n == 1) return A[0]; x1 = spawn add_r(a,n/2); x2 = spawn add_r(a+n/2,n-n/2); sync; return (x1+x2);
5 Παράδειγμα: πρόσθεση Μετατροπή αλγορίθμου συσσωρευτή σε διαίρε και βασίλευε N i=0 A i add(a,n){ res = 0; for (i=0; i<n; i++) res += A[i]; return res; add_r(a, n){ if (n == 1) return A[0]; x1 = spawn add_r(a,n/2); x2 = spawn add_r(a+n/2,n-n/2); sync; return (x1+x2); βελτίωση ταχύτητας;
5 Παράδειγμα: πρόσθεση Μετατροπή αλγορίθμου συσσωρευτή σε διαίρε και βασίλευε N i=0 A i add(a,n){ res = 0; for (i=0; i<n; i++) res += A[i]; return res; add_r(a, n){ if (n < K) return add(a,n); x1 = spawn add_r(a,n/2); x2 = spawn add_r(a+n/2,n-n/2); sync; return (x1+x2); βελτίωση ταχύτητας;
6 Παράδειγμα: Αναζήτηση Χώρος λύσεων (πχ AI) Δομή δεδομένων. DF_Search(node): for c in node.get_children(): DF_Search(c)
6 Παράδειγμα: Αναζήτηση Χώρος λύσεων (πχ AI) Δομή δεδομένων. DF_Search(node): for c in node.get_children(): spawn DF_Search(c) sync
Παράδειγμα: Αναζήτηση Χώρος λύσεων (πχ AI) Δομή δεδομένων. DF_Search(node): for c in node.get_children(): spawn DF_Search(c) sync Αν βρω αυτό που ψάχνω; (ή ανακαλύψω ότι η αναζήτηση σε ένα τμήμα του χώρου είναι ανευ ουσίας;) 6
7 Παράδειγμα: πολλαπλασιασμός N i=0 A i mul(a,n){ res = 1; for (i=0; i<n; i++) res *= A[i]; return res; mul_r(a, n){ if (n == 1) return A[0]; x1 = spawn mul_r(a,n/2); x2 = spawn mul_r(a+n/2,n-n/2); sync; return (x1*x2); Τι (αλγοριθμική) βελτίωση μπορώ να κάνω;
7 Παράδειγμα: πολλαπλασιασμός N i=0 A i mul(a,n){ res = 1; for (i=0; i<n; i++) res *= A[i]; return res; mul_r(a, n){ if (n == 1) return A[0]; x1 = spawn mul_r(a,n/2); x2 = spawn mul_r(a+n/2,n-n/2); sync; return (x1*x2); Τι (αλγοριθμική) βελτίωση μπορώ να κάνω; Όταν συναντήσω το 0, μπορώ να σταματήσω!
Cilk keywords - Advanced : Inlets (1) 8 inlet: Function ορισμένο εσωτερικά σε ένα Cilk procedure 1. Παρέχει τη δυνατότητα στον προγραμματιστή να χειριστεί την τιμή που επιστρέφει το spawned thread 2. void function 3. Δεν μπορεί να περιέχει spawn ή sync 4. Μόνο το πρώτο argument μπορεί να είναι ένα spawn call 5. Εγγυάται atomicity
9 Cilk keywords - Advanced : Inlets (2). int max, ix = -1; inlet void update(int val, int index) { if (ix == -1 val > max) { ix = index; max = val; for(i=0; i<1000000; i++) update(spawn foo(i), i); sync;. 1. Αρχικά αποτιμούνται τα non-spawn args του update 2. Γίνεται spawned η cilk procedure foo(i) 3. Συνεχίζεται η εκτέλεση του επόμενου statement 4. Όταν η foo(i) επιστρέψει καλείται η update()
Cilk keywords - Advanced : Inlets (3) 10. cilk int wfib(int n) { if (n == 0) return 0; else { int i, x = 1; for(i=0; i<=n-2; i++) x += spawn wfib(i); sync; return x;. Όταν o cilk compiler εντοπίσει ένα assignment operator παράγει αυτόματα ένα implicit inlet για να εκτέλεσει την πράξη.
11 Cilk keywords - Advanced : Abort abort 1. Μπορεί να χρησιμοποιηθεί μόνο στο εσωτερικό ενός inlet 2. Tερματίζει ασφαλώς τη λειτουργία των διαδικασιών που έγιναν spawned από τον πατέρα Πιθανά προβλήματα 1. Καμία εγγύηση για το πότε τερματίζεται η λειτουργία ενός παιδιού Mπορεί να μην τερματιστεί άμεσα Είναι πιθανό να προλάβει να ολοκληρώσει τη λειτουργία του Best effort διαδικασία χωρίς εγγυήσεις 2. Κατά το sync, τα παιδιά που έγιναν abort δε θα έχουν κάνει set τα return values τους Υπεύθυνος ο προγραμματιστής 3. Δεν υπάρχει καμιά εγγύηση για το εάν ένα abort θα σκοτώσει ή όχι μελλοντικά παιδιά Εξαρτάται από το πότε έγιναν spawned Mόνο τα παιδιά μετά από το sync είναι εγγυημένο ότι δε θα σκοτωθούν εξαιτίας ενός abort που εκτελείται πριν από το sync
12 Παράδειγμα: πολλαπλασιασμός inlet/abort: έλεγχος για 0 int mul(int *A,int n){ int p = 1; for (int i=0; i<n; i++) p *= A[i]; return p;. cilk int mul_r(int *A, int n){ int p = 1; if (n == 1) return A[0]; p *= spawn mul_r(a,n/2); p *= spawn mul_r(a+n/2,n-n/2); sync; return p;.
12 Παράδειγμα: πολλαπλασιασμός inlet/abort: έλεγχος για 0 1. Προσθήκη ενός explicit inlet int mul(int *A,int n){ int p = 1; for (int i=0; i<n; i++) p *= A[i]; return p;. cilk int mul_r(int *A, int n){ int p = 1; inlet void mult(int x) { p *= x; return; if (n == 1) return A[0]; mult(spawn mul_r(a,n/2); mult(spawn mul_r(a+n/2,n-n/2); sync; return p;.
12 Παράδειγμα: πολλαπλασιασμός inlet/abort: έλεγχος για 0 1. Προσθήκη ενός explicit inlet 2. Έλεγχος για 0 στο εσωτερικό του inlet int mul(int *A,int n){ int p = 1; for (int i=0; i<n; i++) p *= A[i]; return p;. cilk int mul_r(int *A, int n){ int p = 1; inlet void mult(int x) { p *= x; if (p == 0) abort; return; if (n == 1) return A[0]; mult(spawn mul_r(a,n/2); mult(spawn mul_r(a+n/2,n-n/2); sync; return p;.
Παράδειγμα: πολλαπλασιασμός inlet/abort: έλεγχος για 0 1. Προσθήκη ενός explicit inlet 2. Έλεγχος για 0 στο εσωτερικό του inlet 3. Extra έλεγχος για future children. int mul(int *A,int n){ int p = 1; for (int i=0; i<n; i++) p *= A[i]; return p; cilk int mul_r(int *A, int n){ int p = 1; inlet void mult(int x) { p *= x; if (p == 0) abort; return; if (n == 1) return A[0]; mult(spawn mul_r(a,n/2); if (p == 0) return 0; mult(spawn mul_r(a+n/2,n-n/2); sync; return p;. 12
Cilk keywords - Advanced : SYNCHED 13 SYNCHED : Ο προγραμματιστής μπορεί να ελέγξει αν μια cilk procedure είναι synched ή όχι, ελέγχοντας την τιμή της ψευδομεταβλητής SYNCHED. SYNCHED = 0 : Κάποια παιδιά δεν έχουν ακόμα ολοκληρώσει τη λειτουργία τους SYNCHED = 1 : Όλα τα παιδιά έχουν ολοκληρώσει τη λειτουργία τους
Μοντέλο Επίδοσης Cilk 14. - T p : χρόνος εκτέλσης σε P επεξ.
Μοντέλο Επίδοσης Cilk 14. - T p : χρόνος εκτέλσης σε P επεξ. - T 1 : work (κόστος εκτέλεσης όλων των κόμβων)
Μοντέλο Επίδοσης Cilk. - T p : χρόνος εκτέλσης σε P επεξ. - T 1 : work (κόστος εκτέλεσης όλων των κόμβων) - T : span (κόστος εκτέλεσης για επεξ.) 14
Μοντέλο Επίδοσης Cilk 14. - T p : χρόνος εκτέλσης σε P επεξ. - T 1 : work (κόστος εκτέλεσης όλων των κόμβων) - T : span (κόστος εκτέλεσης για επεξ.) - Κάτω όρια: T p T 1 /P, T p T - Μέγιστο speedup: T 1 /T
Μοντέλο Επίδοσης Cilk 14. - T p : χρόνος εκτέλσης σε P επεξ. - T 1 : work (κόστος εκτέλεσης όλων των κόμβων) - T : span (κόστος εκτέλεσης για επεξ.) - Κάτω όρια: T p T 1 /P, T p T - Μέγιστο speedup: T 1 /T - Ο χρόνοδρομολογητής της Cilk επιτυγχάνει T p = T 1 /P + O(T ) (γραμμικό speedup εάν P T 1 /T ) απαιτεί S p PS 1 χώρο στη στοίβα έχει καλή επίδοση και στην πράξη
Parallel slack (task grain) 15 P T 1 /T parallel slack: (T 1 /T ) /P Παραλληλισμός προγράμματος T1 /T Παραλληλισμός μηχανήματος P Χρειάζεται τάξης μεγέθους περισσότερος παραλληλισμός στο πρόγραμμα από ότι στο πραγματικό σύστημα. Διαισθητικά: + Πρόγραμμα δεν εξαρτάται από αριθμό επεξεργαστών + Συνεισφέρει στην αποδοτική χρονοδρομολόγηση + Επιτρέπει καλύτερη κατανομή φόρτου (load balancing) αλλά: - Αν το μέγεθος δουλειάς της κάθε εργασίας μικρό, τότε οι επιβαρύνσεις του συστήματος επηρεάζουν σημαντικά
16 Ισοκατανομή φόρτου - μέγεθος εργασίας load balancing - task grain κόστος uniform. εργασίες Σταθερό κόστος εκτέλεσης: 1 εργασία / πραγματικό επεξεργαστή
16 Ισοκατανομή φόρτου - μέγεθος εργασίας load balancing - task grain κόστος? uniform. εργασίες Σταθερό κόστος εκτέλεσης: 1 εργασία / πραγματικό επεξεργαστή Το κόστος εκτέλεσης είναι μη-σταθερό και άγνωστο Μεγάλο parallel slack
17 Χρονοδρομολόγηση στο χώρο χρήστη user-level scheduling Αριθμός εργασιών T P επεξεργαστές (kernel threads) Η κάθε εργασία μπορεί να παράγει άλλες... να περιμένει την ολοκλήρωση των παιδιών της. Στόχοι Ισοκατανομή φόρτου εργασίας Αποδοτική χρήση χώρου Μικρή επιβάρυνση (ανεξάρτητη του T) Πότε εκτελείται ο ΧΔ;
17 Χρονοδρομολόγηση στο χώρο χρήστη user-level scheduling Αριθμός εργασιών T P επεξεργαστές (kernel threads) Η κάθε εργασία μπορεί να παράγει άλλες... να περιμένει την ολοκλήρωση των παιδιών της. Στόχοι Ισοκατανομή φόρτου εργασίας Αποδοτική χρήση χώρου Μικρή επιβάρυνση (ανεξάρτητη του T) Πότε εκτελείται ο ΧΔ; Εισάγονται κατάλληλες κλήσεις στις spawn/sync/κλπ
18 Τακτικές χρονοδρομολόγησης work sharing: Όταν δημιουργούνται νέες εργασίες ο ΧΔ προσπαθεί να τις "στείλει" σε ανενεργούς επεξεργαστές. work stealing: Οι ανενεργοί επεξεργαστές προσπαθούν να "κλέψουν" εργασίες. Γενικά προτιμάται η τακτική work stealing (στη Cilk και όχι μόνο) καλύτερο locality μικρότερη επιβάρυνση συγχρονισμού Βέλτιστα θεωρητικά όρια ως προς χρόνο, χώρο [Blumofe and Leiserson '99]
19 work stealing Χρονοδρομολόγηση στη Cilk P 2 Μια ουρά ΧΔ ανά P deque (double-ended queue) pushbot popbot poptop top bot T x T y T z T p P. 1
19 work stealing Χρονοδρομολόγηση στη Cilk P 2 Μια ουρά ΧΔ ανά P deque (double-ended queue) pushbot popbot poptop top bot T x T y T z T p T c T p spawn task T c pushbot(tp ) execute(t c ) (FAST!) P. 1
19 work stealing Χρονοδρομολόγηση στη Cilk P 2 Μια ουρά ΧΔ ανά P deque (double-ended queue) pushbot popbot poptop top bot T y T z T p T c T p spawn task T c pushbot(tp ) execute(t c ) (FAST!) P. 1 P 1 ανενεργός Τυχαία επιλογή επεξεργαστή p p->poptop() Εκτέλεση task που προέκυψε (SLOW!) T x
Υλοποίηση ΧΔ χώρου χρήστη 20 Για να υποστηρίζεται η κλοπή εργασίων πρέπει η κάθε εργασία να μπορεί να μεταφερθεί σε διαφορετικό επεξεργαστή Τι χρείαζεται η κάθε διεργασία για να εκτελεστεί; Κώδικα (πχ δείκτη σε συνάρτηση) Θέση στον κώδικα Δεδομένα: Στοίβα Σωρός Η Cilk υλοποιεί cactus stack
Cactus stack A. B C D E Εικόνα της κάθε συνάρτησης: A. B C D E A A A A A B C C C D E 21
Μεταγλώττιση και εκτέλεση προγραμμάτων Cilk 22 $ cilkc fib.cilk -o fib
Μεταγλώττιση και εκτέλεση προγραμμάτων Cilk 22 $ cilkc fib.cilk -o fib $./fib --nproc 2 --stats 1 -- 35 Result: 9227465 RUNTIME SYSTEM STATISTICS: Wall-clock running time on 2 processors: 2.114348 s
Μεταγλώττιση και εκτέλεση προγραμμάτων Cilk 22 $ cilkc fib.cilk -o fib $./fib --nproc 2 --stats 1 -- 35 Result: 9227465 RUNTIME SYSTEM STATISTICS: Wall-clock running time on 2 processors: 2.114348 s $./fib --nproc 8 --stats 1 -- 35 Result: 9227465 RUNTIME SYSTEM STATISTICS: Wall-clock running time on 8 processors: 538.614000 ms
Μεταγλώττιση και εκτέλεση προγραμμάτων Cilk 22 $ cilkc fib.cilk -o fib $./fib --nproc 2 --stats 1 -- 35 Result: 9227465 RUNTIME SYSTEM STATISTICS: Wall-clock running time on 2 processors: 2.114348 s $./fib --nproc 8 --stats 1 -- 35 Result: 9227465 RUNTIME SYSTEM STATISTICS: Wall-clock running time on 8 processors: 538.614000 ms $./fib --help Cilk options:...
Cilk Plus 23 Επέκταση της C και της C++ σχεδιασμένη για multithreaded parallel computing Ιστορία - 2009: Αγορά Cilk arts από Intel - 2010: Release of Intel Cilk Plus. Περιλαμβάνει array extensions και είναι integrated με τα Intel tools(compilers, debuggers, etc). - 2011: Cilk Plus for GCC4.7 (maintained by Intel) http://developer.amd.com/afds/assets/ presentations/2080_final.pdf
Cilk Plus keywords 24 cilk_spawn cilk_sync cilk_for