Προγραμματισμός Συστημάτων Υψηλών Επιδόσεων (ΗΥ421) 3η Εργαστηριακή Άσκηση

Σχετικά έγγραφα
Προγραμματισμός Συστημάτων Υψηλών Επιδόσεων (ΗΥ421) Εργασία Εξαμήνου

Αναφορά (1/2) Μπορούμε να ορίσουμε μια άλλη, ισοδύναμη αλλά ίσως πιο σύντομη, ονομασία για ποσότητα (μεταβλητή, σταθερή, συνάρτηση, κλπ.

ΠΡΟΗΓΜΕΝΟΙ ΜΙΚΡΟΕΠΕΞΕΡΓΑΣΤΕΣ PROJECT 2: MEMORY MANAGEMENT

1. Πότε χρησιμοποιούμε την δομή επανάληψης; Ποιες είναι οι διάφορες εντολές (μορφές) της;

Οργάνωση επεξεργαστή (2 ο μέρος) ΜΥΥ-106 Εισαγωγή στους Η/Υ και στην Πληροφορική

Λύσεις για τις ασκήσεις του lab5

Εισαγωγή στην πληροφορική

Πανεπιστήμιο Θεσσαλίας Τμήμα Μηχανικών Η/Υ, Τηλεπικοινωνιών και Δικτύων

2ο ΓΕΛ ΑΓ.ΔΗΜΗΤΡΙΟΥ ΑΕΠΠ ΘΕΟΔΟΣΙΟΥ ΔΙΟΝ ΠΡΟΣΟΧΗ ΣΤΑ ΠΑΡΑΚΑΤΩ

Οργάνωση Υπολογιστών ΕΛΛΗΝΙΚΗ ΔΗΜΟΚΡΑΤΙΑ ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΡΗΤΗΣ. Ασκήσεις 7: Πρόγραμμα Συνδεδεμένης Λίστας και Διαδικασιών. Μανόλης Γ.Η.

Βρόχοι. Εντολή επανάληψης. Το άθροισμα των αριθμών 1 5 υπολογίζεται με την εντολή. Πρόβλημα. Πώς θα υπολογίσουμε το άθροισμα των ακέραιων ;

SMPcache. Ένα εργαλείο για προσομοίωση-οπτικοποίηση κρυφής μνήμης (Cache)

Προγραμματισμός Ι (HY120)

lab13grades Άσκηση 2 -Σωστά απελευθερώνετε ολόκληρη τη λίστα και την κεφαλή

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ ΣΕ ΓΛΩΣΣΟΜΑΘΕΙΑ

ΣΥΣΤΗΜΑΤΑ ΠΑΡΑΛΛΗΛΗΣ ΕΠΕΞΕΡΓΑΣΙΑΣ 9o εξάμηνο ΗΜΜΥ, ακαδημαϊκό έτος

8. Η δημιουργία του εκτελέσιμου προγράμματος γίνεται μόνο όταν το πηγαίο πρόγραμμα δεν περιέχει συντακτικά λάθη.

FAIL PASS PASS οριακά

Master Mind εφαρμογή στη γλώσσα προγραμματισμού C

Εργαστήριο Δομής και Λειτουργίας Μικροϋπολογιστών. Βοήθημα εκτέλεσης εργαστηριακής άσκησης 3: Εντολές λογικών πράξεων και εντολές κλήσης ρουτινών

Προγραμματισμός συστημάτων UNIX/POSIX. Θέμα επιλεγμένο από τους φοιτητές: Προγραμματιστικές τεχνικές που στοχεύουν σε επιδόσεις

Ασκήσεις στα Προηγμένα Θέματα Αρχιτεκτονικής Υπολογιστών

Εισαγωγή στον Προγραμματισμό

lab13grades 449 PASS 451 PASS PASS FAIL 1900 FAIL Page 1

ΑΝΑΠΤΥΞΗ ΕΦΑΡΜΟΓΩΝ ΣΕ ΠΡΟΓΡΑΜΜΑΤΙΣΤΙΚΟ ΠΕΡΙΒΑΛΛΟΝ ΕΠΑΝΑΛΗΠΤΙΚΟ ΔΙΑΓΩΝΙΣΜΑ ΠΡΟΣΟΜΟΙΩΣΗΣ ΠΑΝΕΛΛΑΔΙΚΩΝ ΣΧΟΛΙΚΟΥ ΕΤΟΥΣ

Ερωτήσεις πολλαπλής επιλογής - Κεφάλαιο 2. Α1. Ο αλγόριθμος είναι απαραίτητος μόνο για την επίλυση προβλημάτων πληροφορικής

ΕΘΝΙΚΟ ΜΕΤΣΟΒΙΟ ΠΟΛΥΤΕΧΝΕΙΟ ΣΧΟΛΗ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΜΗΧΑΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ ΤΕΧΝΟΛΟΓΙΑΣ ΠΛΗΡΟΦΟΡΙΚΗΣ ΚΑΙ ΥΠΟΛΟΓΙΣΤΩΝ

ΠΑΝΕΠΙΣΤΗΜΙΟ ΘΕΣΣΑΛΙΑΣ ΣΧΟΛΗ ΘΕΤΙΚΩΝ ΕΠΙΣΤΗΜΩΝ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ

Στη C++ υπάρχουν τρεις τύποι βρόχων: (a) while, (b) do while, και (c) for. Ακολουθεί η σύνταξη για κάθε μια:

Προβλήματα, αλγόριθμοι, ψευδοκώδικας

Εικονική Μνήμη (Virtual Μemory)

ΣΕΤ ΑΣΚΗΣΕΩΝ 3. Προθεσµία: 7/1/2014, 22:00

Χρησιμοποιείται για να αποφασίσει το πρόγραμμα αν θα κάνει κάτι σε ένα σημείο της εκτέλεσής του, εξετάζοντας αν ισχύει ή όχι μια συνθήκη.

53 Χρόνια ΦΡΟΝΤΙΣΤΗΡΙΑ ΜΕΣΗΣ ΕΚΠΑΙΔΕΥΣΗΣ Σ Α Β Β Α Ϊ Δ Η Μ Α Ν Ω Λ Α Ρ Α Κ Η

ΑΛΓΟΡΙΘΜΟΙ ΒΑΣΙΚΕΣ ΕΝΝΟΙΕΣ

ΑΝΑΠΤΥΞΗ ΕΦΑΡΜΟΓΩΝ ομή Επανάληψης

Πίνακες. 1 Πίνακες. 30 Μαρτίου 2014

Λέγονται οι αριθμοί που βρίσκονται καθημερινά στη φύση, γύρω μας. π.χ. 1 μήλο, 2 παιδιά, 5 αυτοκίνητα, 100 πρόβατα, δέντρα κ.λ.π.

Αρχιτεκτονική Eckert-von Neumann. Πως λειτουργεί η ΚΜΕ; Κεντρική μονάδα επεξεργασίας [3] ΕΠΛ 031: ΕΙΣΑΓΩΓΗ ΣΤΟΝ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟ

Διάλεξη 04: Παραδείγματα Ανάλυσης

ΑΡΧΙΤΕΚΤΟΝΙΚΗ ΥΠΟΛΟΓΙΣΤΩΝ. Κεφάλαιο 3

2 ΟΥ και 8 ΟΥ ΚΕΦΑΛΑΙΟΥ

Σκοπός. Εργαστήριο 6 Εντολές Επανάληψης

Διάλεξη 04: Παραδείγματα Ανάλυσης Πολυπλοκότητας/Ανάλυση Αναδρομικών Αλγόριθμων

Ερωτήσεις πολλαπλής επιλογής - Κεφάλαιο 2

ΕΠΛ231 Δομές Δεδομένων και Αλγόριθμοι 4. Παραδείγματα Ανάλυσης Πολυπλοκότητας Ανάλυση Αναδρομικών Αλγόριθμων

Εισαγωγή στον Προγραμματισμό

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Sheet1_2. - Δεν απελευθερώνεις τη δυναµικά δεσµευµένη µνήµη. - Η έξοδος του προγράµµατός σου δεν είναι ακριβώς όπως ζητούσε η άσκηση.

Πρόβλημα 37 / σελίδα 207

Παραδείγματα Χρήσης του DrJava

Κεφαλαιο 2.2 ΑΝΑΚΕΦΑΛΑΙΩΤΙΚΕΣ ΑΛΓΟΡΙΘΜΟΙ

Μάθημα 4: Κεντρική Μονάδα Επεξεργασίας

All Pairs Shortest Path

Ενότητα 2. Ζωγραφίζοντας με το ΒΥΟΒ

Διεργασίες (μοντέλο μνήμης & εκτέλεσης) Προγραμματισμός II 1

Φάσμα προπαρασκευή για Α.Ε.Ι. & Τ.Ε.Ι.

Κύρια μνήμη. Μοντέλο λειτουργίας μνήμης. Ένα τυπικό υπολογιστικό σύστημα σήμερα. Οργάνωση Υπολογιστών (ΙI)

Εισαγωγή στην Ανάλυση Αλγορίθμων (1) Διαφάνειες του Γ. Χ. Στεφανίδη

Αριθμητική Ανάλυση & Εφαρμογές

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ ΜΕ ΤΟ ΚΙΤ ΡΟΜΠΟΤΙΚΗΣ LEGO MINDSTORMS EV3

Εισαγωγή στην Αριθμητική Ανάλυση

Εισαγωγή εκτελέσιμου κώδικα σε διεργασίες

Λειτουργικά Συστήματα Η/Υ

ΕΘΝΙΚΟ ΜΕΤΣΟΒΙΟ ΠΟΛΥΤΕΧΝΕΙΟ ΣΧΟΛΗ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΜΗΧΑΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ ΤΕΧΝΟΛΟΓΙΑΣ ΠΛΗΡΟΦΟΡΙΚΗΣ ΚΑΙ ΥΠΟΛΟΓΙΣΤΩΝ

Προγραμματισμός Ι (ΗΥ120)

Σημειωματάριο Τετάρτης 18 Οκτ. 2017

Προγραμματισμός I (Θ)

Λειτουργικά Συστήματα. Τ.Ε.Ι. Ιονίων Νήσων Σχολή Διοίκησης και Οικονομίας - Λευκάδα

Συστήματα Παράλληλης και Κατανεμημένης Επεξεργασίας

Εισαγωγή στην Πληροφορική

Προγραμματισμός Η/Υ (ΤΛ2007 )

Μικροεπεξεργαστές. Σημειώσεις Μαθήματος Υπεύθυνος: Δρ Άρης Παπακώστας,

Περι-γράφοντας... βρόχους

ΦΥΛΛΟ ΔΡΑΣΤΗΡΙΟΤΗΤΩΝ

ΑΣΥΜΠΤΩΤΙΚΗ ΑΝΑΛΥΣΗ & ΠΡΟΣΘΕΣΗ

Διδάσκων: Παναγιώτης Ανδρέου

Ενδεικτικές Ερωτήσεις Θεωρίας

Η Δομή Επανάληψης. Εισαγωγή στην δομή επανάληψης Χρονική διάρκεια: 3 διδακτικές ώρες

Εξωτερική Αναζήτηση. Ιεραρχία Μνήμης Υπολογιστή. Εξωτερική Μνήμη. Εσωτερική Μνήμη. Κρυφή Μνήμη (Cache) Καταχωρητές (Registers) μεγαλύτερη ταχύτητα

ΠΑΡΑΓΡΑΦΟΣ 1. 2 ΠΡΟΣΘΕΣΗ ΑΦΑΙΡΕΣΗ ΚΑΙ ΠΟΛΛΑΠΛΑΣΙΑΣΜΟΣ ΦΥΣΙΚΩΝ ΑΡΙΘΜΩΝ

ΕΡΓΑΣΤΗΡΙΟ 4: Μεταβλητές, Δομές Ελέγχου και Επανάληψης

Ανάπτυξη Εφαρμογών σε Προγραμματιστικό Περιβάλλον

Βαθμός Σχόλια. lab PASS 1194 PASS 1238 PASS 1239 PASS

Κεφάλαιο 8: Προγραμματίζοντας αλγορίθμους έξυπνα και δημιουργικά

ΕΘΝΙΚΟ ΜΕΤΣΟΒΙΟ ΠΟΛΥΤΕΧΝΕΙΟ ΣΧΟΛΗ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΜΗΧΑΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ ΤΕΧΝΟΛΟΓΙΑΣ ΠΛΗΡΟΦΟΡΙΚΗΣ ΚΑΙ ΥΠΟΛΟΓΙΣΤΩΝ

Ψευδοκώδικας. November 7, 2011

Πανεπιστήμιο Θεσσαλίας Τμήμα Μηχανικών Η/Υ, Τηλεπικοινωνιών και Δικτύων

Κατακερματισμός (Hashing)

Το ολοκληρωμένο κύκλωμα μιας ΚΜΕ. «Φέτα» ημιαγωγών (wafer) από τη διαδικασία παραγωγής ΚΜΕ

Αρχιτεκτονική Υπολογιστών Ασκήσεις Εργαστηρίου

Υπολογισμός αθροισμάτων

Κατανεμημένα Συστήματα

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Η/Υ Ακαδημαϊκό έτος ΤΕΤΡΑΔΙΟ ΕΡΓΑΣΤΗΡΙΟΥ #4

Οργάνωση Υπολογιστών (ΙI)

5.1. Προσδοκώμενα αποτελέσματα

ΘΕΜΑ ΕΡΓΑΣΙΑΣ: ΠΕΙΡΑΜΑΤΙΣΜΟΣ ΜΕ ΜΑΘΗΤΗ ΔΗΜΟΤΙΚΟΥ ΚΑΙ ΕΞΑΓΩΓΗ ΣΥΜΠΕΡΑΣΜΑΤΩΝ

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Μαθήματα από τα εργαστήρια

ΑΝΑΠΤΥΞΗ ΕΦΑΡΜΟΓΩΝ Κεφάλαιο 3 ο

Κεφάλαιο 2 ο Βασικές Έννοιες Αλγορίθμων (σελ )

Transcript:

Προγραμματισμός Συστημάτων Υψηλών Επιδόσεων (ΗΥ421) 3η Εργαστηριακή Άσκηση ΟΝΟΜΑ: Ιωαννίδης Σταύρος ΑΕΜ: 755 Αποτελέσματα devicequery Profiling με το Vtune

Το profiling έδειξε πως οι πιο αργές συναρτήσεις είναι οι histogram_equalization και η histogram. Ο υπόλοιπος χρόνος πηγαίνει σε ανάγνωση/εγγραφή της εικόνας από/στο δίσκο. Αν τρέξουμε σε διαφορετικό μηχάνημα από τον inf-quad (στο οποίο βρίσκεται και ο δίσκος) παρατηρούμε πως οι χρόνοι ανάγνωσης/εγγραφής αυξάνονται σημαντικά, το οποίο είναι και λογικό αφού τα δεδομένα της εικόνας έχουν να περάσουν και πάνω από το ethernet. Συνεπώς για να εκμεταλλευτούμε τους πόρους (GPU) που διατίθενται από τα άλλα μηχανήματα (infzeus1 και inf-zeus2) στις προσπάθειες βελτιστοποίησης της απόδοσης που ακολουθούν, θα λαμβάνουμε μετρήσεις του χρόνου τοπικά για κάθε τμήμα του κώδικα που μας ενδιαφέρει, αγνοώντας τον χρόνο ανάγνωσης/εγγραφής της εικόνας. Οι μετρήσεις χρόνου γίνονται με την gettimeofday και χρησιμοποιώντας το εκτελέσιμο με μέγιστο βαθμό βελτιστοποιήσεων (-Ο4). Ως πρώτη ενέργεια τρέχουμε έναν κενό kernel (dummy kernel) ο οποίος απαιτεί χρόνο 75-450 msec και δεν τον λαμβάνουμε υπόψη στις μετρήσεις. Διατρέχοντας τον κώδικα παρατηρούμε πως στις συναρτήσεις που μας ενδιαφέρουν έχουμε να κάνουμε με 2 είδη loop: 1. αυτά που κάνουν 256 επαναλήψεις, των οποίων η βελτιστοποίηση δεν μας απασχολεί διότι ολοκληρώνονται γρήγορα σε χρόνο 1-4 μsec (μετά από μετρήσεις), 2. αυτά που κάνουν img_size επαναλήψεις, που μας ενδιαφέρει να βελτιστοποιηθούν διότι εκεί πηγαίνει ο περισσότερος χρόνος εκτέλεσης. Στην ουσία αυτά διατρέχουν όλη την εικόνα. Για λόγους ευκολότερης ανάγνωσης του κώδικα αντικαταστήσαμε τις κλήσεις των συναρτήσεων histogram και histogram-equalization (στην contrast_enhancement_g) με τις ίδιες τις συναρτήσεις. Βελτιστοποίηση της histogram Η histogram αρχικοποιεί τον πίνακα hist[256] σε 0 και τον γεμίζει διατρέχοντας την εικόνα. Η πρώτη σκέψη ήταν να μεταφέρουμε τον κώδικα σε GPU και κάθε νήμα να αναλάβει την ανάγνωση ενός pixel την εικόνας (δηλ. ενός στοιχείου του πίνακα img_in.img) και να αυξήσει την τιμή στην ανάλογη θέση του hist. Στην ουσία είχαμε δεκάδες εκατομμύρια νήματα να προσπαθούν να αλλάξουν τις ίδιες 256 θέσεις μνήμης ταυτόχρονα προφανώς με απογοητευτικά αποτελέσματα (από άποψη ορθότητας και χρόνου). Δεύτερη σκέψη ήταν κάθε block να αναλάβει να ασχοληθεί μόνο με ένα τμήμα της εικόνας (διαισθητικά επιλέξαμε τμήματα των 48ΚΒ. Τα τμήματα στον κώδικα αναφέρονται ως parts) υπολογίζοντας ένα μερικό πίνακα hist το καθένα, έτσι ώστε το άθροισμα όλων των επιμέρους hist να δίνει τον τελικό πίνακα hist. Τώρα κάθε νήμα θα αναλάβει τον υπολογισμό μιας θέσης του μερικού πίνακα hist (που αντιστοιχεί στο συγκεκριμένο block), διατρέχοντας όλο το τμήμα της εικόνας που του αναλογεί (48ΚΒ). Συνεπώς χρειάζονται blocks των 256 νημάτων όσες δηλαδή και οι θέσεις του hist. Μετά την εκτέλεση του Kernel, οι μερικοί πίνακες hist μεταφέρονται πίσω στη CPU και εκεί υπολογίζεται ο τελικός πίνακας hist. Για βελτίωση του χρόνου προσπέλασης μνήμης, αποθηκεύσαμε το άθροισμα που υπολογίζει το κάθε thread σε καταχωρητή. Αυτό μας απάλλαξε και από την υποχρέωση αρχικοποίησης των μερικών πινάκων hist σε μηδέν. Εικόνα Χρόνος CPU Χρόνος Kernel πριν την χρήση καταχωρητή Χρόνος Kernel με χρήση καταχωρητή Συνολικός χρόνος GPU πριν την χρήση καταχωρητή Συνολικός χρόνος GPU με χρήση καταχωρητή uth 2379 14507 9974 15275 10786 x-ray 10558 14949 12184 16197 13420 ship 18909 56313 46423 60448 50581 planet_surface 56490 197374 191002 213481 207065

Σύντομος σχολιασμός: Παρατηρούμε πως η βελτίωση είναι πιο μεγάλη για μικρές εικόνες. Αυτό οφείλετε στο οτι κάθε thread καθενός block διαβάζει 48Κ φορές από την device memory ανεξάρτητα από το ποιο είναι το μέγεθος της εικόνας. Το μόνο που αυξάνει καθώς αυξάνει το μέγεθος της εικόνας είναι το πλήθος τον τμημάτων που σπάμε την εικόνα (parts). Έτσι η βελτιστοποίηση αυτή μας γλιτώνει από έναν χρόνο γύρω στα 6000 μsec που είναι σχεδόν σταθερός. Μπορούμε τώρα να μεταφέρουμε στην GPU και τον υπολογισμό του τελικού hist αθροίζοντας τους μερικούς πίνακες hist. Απλά αφού συγχρονίσουμε τα threads για να σιγουρευτούμε πως όλα έχουν τελειώσει με τον υπολογισμό των μερικών hist που τους είχε ανατεθεί, βάζουμε τα threads ενός μόνο block (π.χ. του block 0) να υπολογίσουν την τιμή ενός στοιχείου του τελικού πίνακα hist, αθροίζοντας τα στοιχεία της αντίστοιχης θέσης όλων των μερικών πινάκων hist. Μερικές μετρήσεις πριν και μετά την παραπάνω κίνηση: Εικόνα Χρόνος Kernel πριν Χρόνος Kernel μετά Συνολικός χρόνος GPU πριν Συνολικός χρόνος GPU μετά Βελτίωση συνολικού χρόνου GPU uth 9974 11861 10786 12591-16,73% x-ray 12184 12262 13420 13316 0,77% ship 46423 42336 50581 45695 9,66% planet_surface 191002 173917 207065 187117 9,63% Για τις μικρές εικόνες ο χρόνος χειροτερεύει. Όμως για τις μεγάλες έχουμε βελτίωση και γι' αυτό ίσως αξίζει τον κόπο να προσπαθήσουμε να βελτιστοποιήσουμε την νέα μας υλοποίηση. Μετά από δοκιμές της ορθότητας των αποτελεσμάτων φάνηκε πως η υλοποίηση αυτή παρήγαγε εσφαλμένα αποτελέσματα για μερικές εκτελέσεις. Γι' αυτό έφταιγε κατά πάσα πιθανότητα η έλλειψη συγχρονισμού μεταξύ των threads διαφορετικών blocks. Αυτό δεν επιτυγχάνεται με την syncthreads() η οποία συγχρονίζει τα threads μέσα σε ένα block μόνο. Η λύση ήταν να υπολογίζουμε το άθροισμα των μερικών πινάκων hist σε έναν ξεχωριστό Kernel, συγχρωνίζονταν τους Kernels με cudathreadsynchronize(). Άλλος τρόπος βελτίωσης του χρόνου είναι με atomics. Τρέξαμε τον Kernel που υπολογίζει το άθροισμα των μερικών πινάκων hist, με την γεωμετρία όμως που είχε και ο πρώτος Kernel που υπολόγιζε τους μερικούς hist και βάλαμε κάθε thread να κοιτάει ένα στοιχείο ενός μόνο μερικού πίνακα hist και να συνεισφέρει στο άθροισμα. Αυτό βελτίωσε λίγο τον χρόνο της GPU. Η επόμενη δοκιμή βελτίωσης του χρόνου αθροίσματος των μερικών hist ήταν με strides. Πιο συγκεκριμένα στο πρώτο βήμα κάθε άρτιο block (δηλ. 1 στα 2) υπολόγιζε ένα μερικό πίνακα hist που προέκυπτε από το άθροισμα του μερικού πίνακα hist που αντιστοιχούσε σε αυτό το block και του διπλανού του. Στο δεύτερο βήμα το ίδιο έκανε μόνο το 1 στα 4 blocks, στο τρίτο το 1 στα 8 κοκ. Ο Kernel υπολόγιζε μόνο το άθροισμα μεταξύ δύο θέσεων μερικών πινάκων hist:

ενώ η κλήση του Kernel γινόταν επαναληπτικά: Η κίνηση αυτή έγινε με το σκεπτικό της εξάλειψης του race condition που υπήρχε στις θέσεις του τελικού πίνακα hist και κατ' επέκταση της αφαίρεσης της atomicadd. Όμως αυτό είχε αρνητικές επιπτώσεις στον χρόνο εκτέλεσης του Kernel πιθανώς λόγω των πολλαπλών Kernel invocations. Συνεπώς η κίνηση απορρίφθηκε. Αφού καταλήξαμε στην υλοποίηση με atomics, ίσος θα ήταν καλύτερα να ενσωματώσουμε την atomicadd στον πρώτο Kernel που υπολογίζει τα μερικά hist και να γλιτώσουμε την κλήση του δεύτερου Kernel. Στην ουσία δεν χρησιμοποιούμε πια μερικούς πίνακες hist, αλλά προσθέτουμε το υπολογισθέν μερικό άθροισμα κατευθείαν επάνω στον hist. Επιστρέφοντας πάλι στον υπολογισμό των μερικών αθροισμάτων hist, μπορούμε να χρησιμοποιήσουμε την shared memory για να αποθηκεύσουμε τα 48Κ pixels που διαβάζουν όλα τα threads ενός block. Κάθε thread αναλαμβάνει να φέρει από την global memory στην shared 48Κ/256 bytes ώστε συνολικά τα 256 threads ενός block να φέρουν 48K bytes. Επειδή για PART_SIZE = 48K οι μετρήσεις έδειξαν πως ο χρόνος του Kernel ήταν τώρα πιο αργός, πειραματιστήκαμε και με άλλα μεγέθη part (δίνεται και ο χρόνος Kernel πριν την χρήση shared memory για σύγκριση): Εικόνα Χωρίς shared 48Κ 32Κ 16Κ 8Κ 4Κ 2K 1K uth 11861 12012 10973 7214 5478 5272 6136 6048 x-ray 12262 16437 16642 11703 8648 8388 9921 9924 ship 42336 57425 58519 39714 31603 31298 38460 38445 planet_surface 173917 229523 234741 156713 127145 126949 156141 156870 Βλέπουμε πως για PART_SIZE κοντά στο 4Κ έχουμε την μεγαλύτερη βελτίωση. Παρατήρηση: Έτσι όπως έχουμε υλοποιήσει να γίνονται οι αναγνώσεις από την shared memory, αν και όλα τα threads ενός block ζητούν να διαβάσουν από το ίδιο shared memory bank σε κάθε επανάληψη του εν λόγο for-loop, παρόλα αυτά δεν έχουμε shared memory bank conflicts διότι τα threads ζητούν να διαβάσουν το ίδιο ακριβώς στοιχείο από την shared memory (broadcast). Άλλη δοκιμή ήταν να βάλουμε τα threads ενός block να διαβάζουν συνεχόμενες θέσεις μνήμης από την global memory, καθώς προσπαθούν να φέρουν τα δεδομένα τους στην shared. Πιο συγκεκριμένα η τροποποίηση ήταν: Αυτή η κίνηση όμως δεν βελτίωσε τον χρόνο του Kernel οπότε απορρίφθηκε.

Μέχρι στιγμής το πιο αργό κομμάτι του προγράμματός μας είναι ο βρόχος στον οποίο κάθε thread κοιτάει τα 4KB (PART_SIZE) που του αναλογούν και υπολογίζει ένα μερικό άθροισμα: Μια καλή βελτιστοποίηση που μπορούμε να κάνουμε είναι loop unrolling. Παρατηρούμε πως το πλήθος των επαναλήψεων του βρόχου είναι ή 4K για όλα -πλην του τελευταίου- blocks ή img_size-start_pixel για το τελευταίο block: Για τα πρώτα μπορούμε να πειραματιστούμε με το loop unrolling άφοβα σε οποιοδήποτε βαθμό. Όμως το τελευταίο block θα είχε τον κίνδυνο να βγει εκτώς ορίων του πίνακα img_in_part, αν για παράδειγμα το img_size-start_pixel ήταν περιττός αριθμός και το loop μας προχωρούσε με βήμα 2. Για την αντιμετώπιση της παραπάνω κατάστασης θα μπορούσαμε να υποχρεώσουμε το img_size να είναι πολλαπλάσιο του 4Κ (ή του βήματος που θα αποφασίζαμε τελικώς να έχει το loop μας) γεμίζοντας στον img_in τα δεδομένα που λείπουν με κενά. Όμως δεν θα υπήρχε η δυνατότητα να αγνοήσουμε αυτούς τους κενούς χαρακτήρες λόγω του ότι όλο το φάσμα 0-255 ενός unsigned char χρησιμοποιείται στον υπολογισμό του μερικού αθροίσματος. Συνεπώς αυτή η λύση δεν γίνεται να εφαρμοστεί. Ένας άλλος τρόπος είναι να εξαναγκάσουμε το τελευταίο block να εκτελεί τον βρόχο χωρίς loop unrolling. Παρατίθενται μερικές μετρήσεις της υλοποίησης αυτής, του χρόνου του Kernel σε σχέση με τα διάφορες τιμές που δοκιμάζουμε να δώσουμε ως βήμα του unrolled βρόχου: Εικόνα Χωρίς loop unrolling 2 4 16 64 128 uth 5136 2815 2369 2138 2096 2077 x-ray 8269 4595 3965 3599 3537 3520 ship 31079 17571 14719 13255 13031 12905 planet_surface 126220 71489 59929 53838 52869 52384 Για loop unrolling σε ακόμα μεγαλύτερο βαθμό ίσως να πετυχαίναμε καλύτερο χρόνο εκτέλεσης, αλλά θα σταματήσουμε στο 128 για να μην γίνει πολύ μεγάλος ο κώδικας. Δοκιμάζοντας την τεχνική του loop unrolling και στον βρόχο όπου κάθε thread φέρνει τα δεδομένα που του αντιστοιχούνε από την global στην shared, η βελτίωση είναι τόσο μικρή που θεωρείται αμελητέα.

Βελτιστοποίηση της histogram-equalization Η histogram-equalization υπολογίζει αρχικά τον πίνακα lut (δεν θα ασχοληθούμε με την βελτιστοποίηση αυτού του τμήματος διότι όπως αναφέρεται και παραπάνω έχει πολύ μικρό χρόνο εκτέλεσης). Έπειτα υπολογίζει την τελική εικόνα χρησιμοποιώντας τον πίνακα lut (μια for με img_size επαναλήψεις): Η μεταφορά αυτού του κώδικα σε GPU ήταν και η πρώτη μας κίνηση. Κάθε thread κάνει την δουλειά μιας επανάληψης της for. Χρησιμοποιούμε τον μέγιστο δυνατό αριθμό threads ανά block που στο device μας είναι 1024. Επειδή υπάρχει περίπτωση το block που θα ασχολείται με τα τελευταία pixel της εικόνας να είναι μικρότερο από 1024, θα έπρεπε με κατάλληλες if να ελέγχουμε την περίπτωση αυτή. Όμως για να αποφύγουμε την χρήση if, επιλέγουμε να δώσουμε παραπάνω δουλειά στη GPU, δίνοντας και στο τελευταίο block 1024 threads. Αυτό προϋποθέτει δέσμευση λίγο μεγαλύτερου από τον αναμενόμενο χώρο στην GPU για τους πίνακες img_in και img_out. Φυσικά τα περίσσια αποτελέσματα που υπολογίστηκαν (πέραν δηλαδή του img_size) θα αγνοούνται. Βασιζόμενοι στην παρατήρηση οτι το img_in δεν μεταβάλλεται κατά τον υπολογισμό του lut, μπορούμε να χρησιμοποιήσουμε το ίδιο img_in που είχαμε μεταφέρει και αποθηκεύσει στην GPU πριν την κλήση του πρώτου kernel, γλιτώνοντας έτσι μια αρκετά χρονοβόρα μεταφορά μνήμης. Για τον κώδικα τώρα παρατηρούμε πως η μοναδική if που υπάρχει μέσα στην for καθυστερεί πολύ την εκτέλεση. Η δουλειά που κάνει αυτή η if όμως, μπορεί να γίνει και μόνο με αριθμητικές πράξεις ώστε να αποφύγουμε το divergence στην GPU, ως εξής: Έτσι το b λειτουργεί σαν boolean που δηλώνει αν το a είναι μεγαλύτερο του 256 (b=true) ή όχι (b=false). Ο πολλαπλασιασμός με το boolean αυτό και το συμπληρωματικό του, στην ουσία δίνει μία μόνο τιμή στο img_out[i]: το 255 ή το a. Παρατίθενται οι μετρήσεις του χρόνου εκτέλεσης του Kernel πριν και μετά την αντικατάσταση της if με αριθμητικές πράξεις: Εικόνα Χρόνος Kernel με if Χρόνος Kernel με αριθμητικές πράξεις uth.pgm 133 159 x-ray.pgm 218 253 ship.pgm 787 905 planet_surface.pgm 3289 3684 Δυστυχώς οι αριθμητικές πράξεις αποδείχθηκαν πιο χρονοβόρες υλοποίηση με την if. οπότε κρατάμε την

Σε μια δεύτερη προσπάθεια να εξαλείψουμε την if, παρατηρούμε πως το αποτέλεσμά της εξαρτάται μόνο από τον πίνακα lut και συγκεκριμένα από το αν η συγκεκριμένη τιμή του lut είναι εντός ή εκτός του ορίου 255. Ίσως είναι πιο σοφό να εκτελέσουμε ένα βρόχο που να κάνει αυτή την δουλειά σε 256 επαναλήψεις (όσο και το μέγεθος του lut) αντί να έχουμε την if να εκτελείται εκατομμύρια φορές, μία για κάθε pixel. Ίσως η πιο κατάλληλη θέση να το κάνουμε αυτό είναι κατά τον υπολογισμό του lut: *οι γραμμές που προσθέσαμε είναι οι 364-366 Η βελτίωση που πήραμε φαίνεται από τις παρακάτω μετρήσεις: Εικόνα Χρόνος Kernel με if Χρόνος Kernel χωρίς if Βελτίωση uth.pgm 133 121 9,02% x-ray.pgm 218 198 9,17% ship.pgm 787 724 8,01% planet_surface.pgm 3289 2958 10,06% Μια βελτιστοποίηση ως προς τον χώρο που μπορούμε να κάνουμε είναι να χρησιμοποιήσουμε τον ίδιο χώρο στην device memory για τις εικόνες εισόδου και εξόδου. Με άλλα λόγια να κάνουμε τις αλλαγές μας κατευθείαν επάνω στο img_in. Έτσι δεν χρειάζεται να δεσμεύσουμε και να θέσουμε σε 0 τον πίνακα img_out στην device memory, εξοικονομώντας σχεδόν το 50% του χώρου. Συνολική βελτίωση Η συνολική βελτίωση που πετύχαμε φαίνεται στις παρακάτω μετρήσεις: Εικόνα Υπολογισμός hist Υπολογισμός lut Υπολογισμός result Σύνολο CPU GPU CPU CPU GPU CPU GPU Speedup uth 4023 2834 8 2907 708 6938 3550 1,95x x-ray 10612 4492 3 4869 1119 15484 5614 2,76x ship 32741 16298 5 12206 3864 44952 20167 2,23x planet_surface 64090 65597 4 46326 15470 110420 81071 1,36x *οι χρόνοι GPU περιλαμβάνουν και τις μεταφορές δεδομένων από και προς την device *

Τέλος στις παρακάτω εικόνες φαίνονται διάφορες (λάθος) εικόνες που παρήχθησαν κατά την διάρκεια της προσπάθειας βελτιστοποίησης: