ΕΘΝΙΚΟ ΜΕΤΣΟΒΙΟ ΠΟΛΥΤΕΧΝΕΙΟ ΣΧΟΛΗ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΜΗΧΑΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ ΤΕΧΝΟΛΟΓΙΑΣ ΠΛΗΡΟΦΟΡΙΚΗΣ ΚΑΙ ΥΠΟΛΟΓΙΣΤΩΝ ΕΡΓΑΣΤΗΡΙΟ ΥΠΟΛΟΓΙΣΤΙΚΩΝ ΣΥΣΤΗΜΑΤΩΝ www.cslab.ece.ntua.gr ΠΡΟΗΓΜΕΝΑ ΘΕΜΑΤΑ ΟΡΓΑΝΩΣΗΣ ΥΠΟΛΟΓΙΣΤΩΝ Λύσεις Θεμάτων της Εξέτασης Σεπτεμβρίου 2006 21 Σεπτεμβρίου 2006 Θέμα 1 ο (35%): Εξετάζουμε την εκτέλεση του παρακάτω κώδικα ο οποίος αντιγράφει ένα null-terminated string. Οι καταχωρητές r1 και r2 περιέχουν τις διευθύνσεις του πρώτου χαρακτήρα των source και target strings, αντίστοιχα (κάθε χαρακτήρας αποτελείται από 1 byte). (1) Repeat: lb r3,0(r1) ;load char (1byte) (2) sb r3,0(r2) ;store char (3) beqz r3, Exit ;was char NULL? (4) addi r1,r1,1 ;update r1 to point at next char of source string (5) addi r2,r2,1 ;update r2 to point at next char of target string (6) j Repeat (7) Exit: Αρχικά, υποθέτουμε ότι έχουμε αρχιτεκτονική σωλήνωσης (pipelining) 5 σταδίων (IF ID EX MEM WB). Υποθέτουμε επίσης τα εξής: H εγγραφή σε κάποιον καταχωρητή γίνεται στο πρώτο μισό ενός κύκλου, ενώ η ανάγνωση από τον ίδιον καταχωρητή στο δεύτερο μισό του ίδιου κύκλου. Δεν υπάρχουν επιπλέον καθυστερήσεις λόγω ανάγνωσης από τη κύρια μνήμη. Για την εντολή διακλάδωσης χωρίς συνθήκη (6), η διεύθυνση-στόχος της διακλάδωσης ( Repeat ) γίνεται άμεσα γνωστή στο στάδιο IF, ενώ ο μετρητής προγράμματος (PC) ανανεώνεται σύμφωνα με τη διεύθυνση-στόχο αυτή στο ίδιο στάδιο. Για την εντολή διακλάδωσης υπό συνθήκη (3), υπάρχει πρόβλεψη διακλάδωσης που είναι πάντα NOT TAKEN. Στην περίπτωση αυτή, δηλαδή, στο τέλος του σταδίου IF ο μετρητής προγράμματος θα ανανεώνεται ώστε να δείχνει στην επόμενη σειριακά εντολή. O έλεγχος για το αν η πρόβλεψη έγινε σωστά ή όχι γίνεται στο στάδιο ΜΕΜ. Σε περίπτωση λάθος πρόβλεψης, στο ίδιο στάδιο υπολογίζεται και ο σωστός στόχος της διακλάδωσης, και ανανεώνεται κατάλληλα ο μετρητής προγράμματος. Σε αυτή την περίπτωση, το pipeline πρέπει να καθαριστεί ( flush ) από τις εντολές που είχαν εισαχθεί στο pipeline εξαιτίας της εσφαλμένης πρόβλεψης. α) Αρχικά, υποθέτουμε ότι η αρχιτεκτονική σωλήνωσης δε διαθέτει σχήμα προώθησης (forwarding). Για τις 2 πρώτες επαναλήψεις του παραπάνω βρόχου (και θεωρώντας ένα string με τουλάχιστον 2 μη μηδενικούς χαρακτήρες), χρησιμοποιείστε ένα διάγραμμα χρονισμού για να δείξετε τα διάφορα στάδια του pipeline από τα οποία διέρχονται οι εντολές σε αυτό το διάστημα εκτέλεσης. Υποδείξτε και εξηγείστε τους πιθανούς κινδύνους (hazards) που μπορούν να προκύψουν κατά την εκτέλεση, καθώς και τον τρόπο με τον οποίον αυτοί αντιμετωπίζονται. Πόσοι κύκλοι απαιτούνται κατά μέσο όρο για την αντιγραφή ενός χαρακτήρα; (μη λάβετε υπόψη σας τον πιθανώς διαφορετικό αριθμό κύκλων που απαιτούνται για την
αντιγραφή του τελευταίου χαρακτήρα θεωρείστε την μέση περίπτωση, δηλαδή την αντιγραφή κάποιου ενδιάμεσου χαρακτήρα). Το διάγραμμα χρονισμού για τις 2 πρώτες επαναλήψεις του loop είναι αυτό που φαίνεται στον παρακάτω πίνακα. Στις στήλες με τους κύκλους εκτέλεσης, με διαφορετικά χρώματα υποδεικνύονται κύκλοι στους οποίους συμβαίνει ταυτόχρονη εγγραφή και ανάγνωση του ίδιου καταχωρητή (στο πρώτο μισό του κύκλου εγγραφή, στο δεύτερο μισό ανάγνωση). Οι - υποδεικνύουν stalls. Κύκλος 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lb r3,0(r1) IF ID EX MEM WB sb r3,0(r2) IF ID - - EX MEM WB beqz r3, Exit IF - - ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB j Repeat IF ID EX MEM WB lb r3,0(r1) IF ID EX MEM WB sb r3,0(r2) IF ID - - EX MEM WB beqz r3, Exit IF - - ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB j Repeat IF ID EX MEM WB Μεταξύ των εντολών 1 και 2, lb και sb, εμφανίζεται RAW κίνδυνος δεδομένων εξαιτίας του ότι υπάρχει πραγματική εξάρτηση δεδομένων, αφού η store χρειάζεται να διαβάσει το περιεχόμενο του καταχωρητή r3 στον οποίον γράφει η load. Ο κίνδυνος επιλύεται με stalls 2 κύκλων. Μεταξύ των εντολών 1 και 3, υπάρχει πραγματική εξάρτηση δεδομένων, αφού η beqz χρειάζεται να διαβάσει το περιεχόμενο του r3, στον οποίον γράφει η load. Η εξάρτηση αυτή όμως δεν εγείρει RAW κίνδυνο δεδομένων, αφού με τα stalls της προηγούμενης περίπτωσης ο κίνδυνος εξαλείφεται. Μεταξύ των εντολών 4 και 7, και 5 και 8, υπάρχουν πραγματικές εξαρτήσεις δεδομένων (εξαιτίας ροής των τιμών των r1 και r2, αντίστοιχα, ανάμεσα σε αυτές τις εντολές). Οι εξαρτήσεις αυτές όμως δεν εγείρουν κίνδυνο στο pipeline, αφού αυτό είναι κατασκευασμένο ώστε η εγγραφή σε κάποιον καταχωρητή να γίνεται στο πρώτο μισό ενός κύκλου, ενώ η ανάγνωση από τον ίδιον καταχωρητή στο δεύτερο μισό του ίδιου κύκλου. Η εντολή διακλάδωσης υπό συνθήκη 3, δεν προκαλεί κάποιο κίνδυνο ελέγχου, διότι υπάρχει πρόβλεψη διακλάδωσης η οποία γίνεται σωστά για την ακολουθία εντολών που υποδεικνύει η άσκηση, και έτσι δεν εισάγονται stalls εξαιτίας του ότι πρέπει να καθαριστεί το pipeline και να επανα-υπολογιστεί η τιμή του PC. Η εντολή διακλάδωσης χωρίς συνθήκη 6 δεν προκαλεί κίνδυνο ελέγχου, διότι η διεύθυνση-στόχος της διακλάδωσης υπολογίζονται στο στάδιο IF, και η εκτέλεση μπορεί να συνεχίσει από τον επόμενο κύκλο σύμφωνα με τη νέα τιμή του PC χωρίς να εισάγονται επιπλέον stalls. Κατά μέσο όρο απαιτούνται 8 κύκλοι ανά χαρακτήρα (από το στάδιο IF της πρώτης επανάληψης μέχρι το IF της δεύτερης).
β) Για την ίδια ακολουθία εντολών, δείξτε όπως και πριν τον χρονισμό του pipeline, θεωρώντας όμως τώρα ότι υπάρχουν όλα τα δυνατά σχήματα προώθησης. Υποδείξτε σε κάθε περίπτωση τα σημεία όπου γίνεται προώθηση. Πόσοι κύκλοι απαιτούνται κατά μέσο όρο για την αντιγραφή ενός χαρακτήρα; Κύκλος 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lb r3,0(r1) IF ID EX MEM WB sb r3,0(r2) IF ID EX MEM WB beqz r3, Exit IF ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB j Repeat IF ID EX MEM WB lb r3,0(r1) IF ID EX MEM WB sb r3,0(r2) IF ID EX MEM WB beqz r3, Exit IF ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB j Repeat IF ID EX MEM WB Κατά μέσο όρο απαιτούνται 6 κύκλοι ανά χαρακτήρα. γ) Επεκτείνουμε την παραπάνω αρχιτεκτονική σωλήνωσης διπλασιάζοντας το εύρος της σωλήνωσης (εύρος 2 σωληνώσεων). Αναδιατάξτε την ακολουθία των εντολών των προηγούμενων ερωτημάτων ώστε να είναι δυνατόν να εκτελεστεί αποδοτικά σύμφωνα με τα νέα δεδομένα. Με άλλα λόγια, αναδιατάξτε τις εντολές ώστε, κατά το δυνατόν, ανά 2 να είναι ανεξάρτητες μεταξύ τους ώστε να μπορούν να εκτελεστούν παράλληλα, αλλά χωρίς ωστόσο να επηρεάζεται η σημασιολογία και η σωστή ροή εκτέλεσης του προγράμματος. Δείξτε τον χρονισμό του pipeline, θεωρώντας όλα τα δυνατά σχήματα προώθησης, και υποδείξτε τα σημεία όπου γίνεται προώθηση. Πόσοι κύκλοι απαιτούνται κατά μέσο όρο για την αντιγραφή ενός χαρακτήρα; Με διαφορετικά χρώματα υποδεικνύονται οι διαφορετικές σωληνώσεις. Κύκλος 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lb r3,0(r1) IF ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB sb r3,0(r2) IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB beqz r3, Exit IF ID EX MEM WB j Repeat IF ID EX MEM WB lb r3,0(r1) IF ID EX MEM WB addi r1,r1,1 IF ID EX MEM WB sb r3,0(r2) IF ID EX MEM WB addi r2,r2,1 IF ID EX MEM WB beqz r3, Exit IF ID EX MEM WB j Repeat IF ID EX MEM WB
Κατά μέσο όρο απαιτούνται 3 κύκλοι ανά χαρακτήρα. δ) Για να βελτιώσουμε κι άλλο την απόδοση, εφαρμόζουμε την τεχνική του ξεδιπλώματος βρόχων (loop unrolling). Αυτό επιτυγχάνεται ως εξής: πρέπει να επαναλάβουμε n φορές συνολικά τις εντολές (1) έως (3) του αρχικού κώδικα ώστε να αντιγραφούν n διαδοχικοί χαρακτήρες σε μία επανάληψη του loop, και παράλληλα, πρέπει να αυξήσουμε τους καταχωρητές r1 και r2 ώστε να δείχνουν, όχι στον επόμενο, αλλά στον χαρακτήρα που βρίσκεται n θέσεις μπροστά από τον τρέχοντα. Θεωρείστε ότι διατίθενται οι τρόποι διευθυνσιοδότησης +k(r1) και -k(r1) (k ακέραιος), ώστε να μπορούμε να αναφερθούμε στο byte που βρίσκεται k θέσεις μπροστά ή k θέσεις πίσω, αντίστοιχα, σε σχέση με το byte που είναι στη θέση μνήμης που δείχνει ο r1. Για την ακολουθία εντολών των προηγούμενων ερωτημάτων (για την αντιγραφή των 2 πρώτων χαρακτήρων, δηλαδή), εφαρμόστε unrolling 2 φορές ακολουθώντας τις παραπάνω οδηγίες. Θεωρείστε ότι έχετε πάλι διπλή σωλήνωση όπως και πριν, οπότε φροντίστε να αναδιατάξετε όπου είναι δυνατόν την ακολουθία, επιτυγχάνοντας το μέγιστο δυνατό παραλληλισμό. Δείξτε τον χρονισμό του pipeline, θεωρώντας όλα τα δυνατά σχήματα προώθησης, και υποδείξτε τα σημεία όπου γίνεται προώθηση. Πόσοι κύκλοι απαιτούνται κατά μέσο όρο για την αντιγραφή ενός χαρακτήρα; O νέος κώδικας έχει ως εξής: Repeat: Exit: lb r3,0(r1) addi r1,r1,2 sb r3,0(r2) addi r2,r2,2 beqz r3,exit lb r3,-1(r1) sb r3,-1(r2) beqz r3,exit j Repeat Κύκλος 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lb r3,0(r1) IF ID EX MEM WB addi r1,r1,2 IF ID EX MEM WB sb r3,0(r2) IF ID EX MEM WB addi r2,r2,2 IF ID EX MEM WB beqz r3, Exit IF ID EX MEM WB lb r3, -1(r1) IF ID EX MEM WB sb r3,-1(r2) IF ID EX MEM WB beqz r3, Exit IF ID - ΕΧ ΜΕΜ WB j Repeat IF ID EX MEM WB H επόμενη εντολή της j Repeat θα αρχίσει να εκτελείται από τον κύκλο 7 και μετά, στη 2η σωλήνωση. Αυτό συμβαίνει γιατί ο στόχος της εντολής j Repeat γίνεται γνωστός στο τέλος του 5ου
κύκλου (οπότε η επόμενη της j Repeat δεν μπορεί να αρχίσει να εκτελείται στον ίδιο κύκλο, έστω κι αν βρίσκεται σε διαφορετική σωλήνωση), όμως στον 6ο κύκλο υπάρχει stall στη 2η σωλήνωση από την εντολή beqz. Αυτό σημαίνει ότι κατά μέσο όρο θα απαιτούνται (7-1)/2 = 3 κύκλοι ανά χαρακτήρα (δηλαδή, καμία βελτίωση σε σχέση με το ερώτημα γ ). Μπορούμε εναλλακτικά όμως να οδηγήσουμε την επόμενη εντολή της j Repeat στην ίδια σωλήνωση με αυτήν (και όχι στην σωλήνωση που έχει stall-άρει ), οπότε η εκτέλεση θα ξεκινήσει από τον κύκλο 6 και μετέπειτα. Σε αυτή την περίπτωση θα απαιτούνται (6-1)/2 = 2.5 κύκλοι ανά χαρακτήρα. (Σημείωση: αυτό, το ότι κερδίζουμε δηλαδή κύκλους σε σχέση με το ερώτημα γ, μπορεί να φανεί καλύτερα αν εκτελέσουμε το loop για μία ακόμη επανάληψη, δηλαδή για μια ακολουθία εντολών που αντιστοιχεί στην αντιγραφή 4 χαρακτήρων). Θεωρήστε το ακόλουθο κομμάτι κώδικα: Θέμα 2 ο (30%): int i,j; double result, a[110][4]; for(i=0; i<4; i++) for(j=0; j<100; j++) result += a[j][i]*a[j+1][i] + 0.5; Ο πίνακας a περιέχει στοιχεία κινητής υποδιαστολής διπλής ακρίβειας, μεγέθους 8 bytes. Κάνουμε τις εξής υποθέσεις: Το πρόγραμμα εκτελείται σε έναν επεξεργαστή με μόνο ένα επίπεδο κρυφής μνήμης δεδομένων. H κρυφή μνήμη είναι πλήρως συσχετιστική (fully associative), αποτελείται από 100 blocks δεδομένων, και έχει LRU πολιτική αντικατάστασης. Το μέγεθος του block είναι 32 bytes. Υποθέτουμε ότι όλες οι μεταβλητές, πλην των στοιχείων του πίνακα a, μπορούν να αποθηκευτούν σε καταχωρητές του επεξεργαστή, οπότε οποιαδήποτε αναφορά σε αυτές δεν συνεπάγεται προσπέλαση στην κρυφή μνήμη. Επίσης, ο επεξεργαστής στέλνει προς εκτέλεση τα loads του προγράμματος, με τη σειρά που αυτά εμφανίζονται στο πρόγραμμα (δηλαδή, πρώτα εκτελεί το load για το στοιχείο a[j][i] και μετά για το a[j+1][i]). Ο πίνακας είναι αποθηκευμένος στην κύρια μνήμη κατά γραμμές. Επιπλέον, είναι ευθυγραμμισμένος ώστε το πρώτο στοιχείο του να απεικονίζεται στην αρχή μιας γραμμής της κρυφής μνήμης. Αρχικά, η κρυφή μνήμη δεδομένων είναι άδεια. α) Βρείτε ποιες από τις αναφορές στα στοιχεία του πίνακα a για όλη την εκτέλεση του παραπάνω κώδικα καταλήγουν σε misses στην cache. Υποδείξτε ποια είναι compulsory, ποια είναι capacity, και ποια conflict. Δώστε τον συνολικό αριθμό των misses. Αφού το μέγεθος του block είναι 32 bytes, κάθε στοιχείο του πίνακα είναι 8 bytes, και ο πίνακας είναι αποθηκευμένος κατά γραμμές, αυτό σημαίνει ότι σε ένα block δεδομένων της κρυφής μνήμης θα απεικονίζονται 4 διαδοχικά (κατά γραμμές) στοιχεία του πίνακα. Μάλιστα, εφόσον ο πίνακας είναι ευθυγραμμισμένος ώστε το 1ο στοιχείο του να απεικονίζεται στην αρχή μιας γραμμής της κρυφής μνήμης, και εφόσον κάθε γραμμή του πίνακα αποτελείται από 4 στοιχεία, αυτό σημαίνει ότι σε ένα block δεδομένων θα απεικονίζεται μια ολόκληρη γραμμή του πίνακα. Συνεπώς, η αναφορά σε οποιδήποτε στοιχείο του πίνακα, θα έχει σαν αποτέλεσμα τη μεταφορά ολόκληρης της αντίστοιχης γραμμής του πίνακα σε ένα block της κρυφής μνήμης. Για i=0, j=0, οι αναφορές στα στοιχεία a[0][0] και a[1][0] αντιστοιχούν σε compulsory misses (καθώς αναφερόμαστε για πρώτη φορά στα αντίστοιχα blocks, και η cache αρχικά είναι άδεια). Μαζί τους, θα
μεταφερθούν και τα υπόλοιπα στοιχεία των αντίστοιχων γραμμών, δηλ. a[0][1], a[0][2], a[0][3] και a[1][1], a[1][2], a[1][3], αντίστοιχα. Κάθε γραμμή θα τοποθετηθεί σε κάποιο block στην cache. Για i=0, j=1, γίνονται αναφορές στα a[1][0] (hit, αφού έχει έρθει στην cache από την προηγούμενη επανάληψη) και a[2][0] (compulsory miss). Έτσι, για τις επαναλήψεις (i,j) = (0,1) έως (0,99), θα έχουμε 1 hit και 1 compulsory miss ανά επανάληψη. Για i=0, λοιπόν, θα έχουμε 2+99=101 misses, τα οποία θα είναι compulsory. Για j=99, με την αναφορά στο a[99][0], θα έχουν γεμίσει και τα 100 blocks της cache με τις πρώτες 100 γραμμές του πίνακα. Συνεπώς η αναφορά στο a[100][0], θα οδηγήσει στην αντικατάσταση του block που περιέχει την 1η γραμμή (a[0][0]-a[0][3]) με το block της 101ης γραμμής (a[100][0]-a[100][3]), λόγω του ότι το block της 1ης γραμμής είναι το least recently used, αφού ήρθε πρώτο στην cache. Έτσι, στην επόμενη επανάληψη (i=1, j=0), η αναφορά στο a[0][1] θα οδηγήσει σε miss, και το block που περιέχει την 1η γραμμή (a[0][0]-a[0][3]) θα έρθει για να αντικαταστήσει το επόμενο least recently used block, δηλαδή το block a[1][0]-a[1][3] της 2ης γραμμής. Κυκλικά, δηλαδή, θα έχουμε αντικαταστάσεις blocks, οπότε για τις αναφορές στις επόμενες επαναλήψεις του εξωτερικού loop, θα είναι στην ουσία σαν να ήταν άδεια η cache. Έτσι τα misses και τα hits θα ακολουθούν το ίδιο μοτίβο όπως και για i=0, με τη διαφορά ότι τα misses τώρα θα είναι capacity, αφού συμβαίνουν λόγω του ότι αντικαταστάθηκαν blocks που είχαν έρθει κατά το παρελθόν λόγω έλλειψης χώρου στην cache (conflict misses δεν έχουμε λόγω του ότι η cache είναι fully associative και τα blocks δεδομένων μπορούν να απεικονιστούν οπουδήποτε στην cache). Συνολικά, λοιπόν, θα έχουμε 4*101=404 misses. β) To σύνολο εντολών της αρχιτεκτονικής του επεξεργαστή διαθέτει μία ειδική εντολή prf(*addr). H εντολή αυτή προ-φορτώνει στην κρυφή μνήμη ολόκληρο το μπλοκ που περιέχει τη λέξη που βρίσκεται στη διεύθυνση μνήμης addr. Χωρίς να αλλάξετε τη σειρά των loads για τις αναφορές στα στοιχεία του πίνακα, εισάγετε κλήσεις στην prf (1 ή περισσότερες) στον παραπάνω κώδικα ώστε να μειωθούν τα misses. Δώστε τον συνολικό αριθμό των misses. Υποθέστε ότι 7 επαναλήψεις του εσωτερικού loop (συμπεριλαμβανομένων των όποιων κλήσεων στην prf) είναι αρκετές ώστε να καλυφθεί ο χρόνος που απαιτείται για να έρθουν τα δεδομένα που ζητά η prf στην cache. Επιπλέον, μη λάβετε ειδική μέριμνα για την προφόρτωση δεδομένων στις αρχικές επαναλήψεις του loop, ούτε για τις έξτρα προφορτώσεις στις τελευταίες επαναλήψεις. Φροντίστε μόνο να εισάγετε τον ελάχιστο αριθμό εντολών prf, αποφεύγοντας τις όποιες περιττές κλήσεις στην εντολή. Ας δούμε τις αναφορές σε στοιχεία του πίνακα για τις πρώτες επαναλήψεις του loop: (i,j)= (0,0): a[0][0] miss, a[1][0] miss (0,1): a[1][0] hit, a[2][0] miss (0,2): a[2][0] hit, a[3][0] miss (0,3): a[3][0] hit, a[4][0] miss (0,4): a[4][0] hit, a[5][0] miss (0,5): a[5][0] hit, a[6][0] miss (0,6): a[6][0] hit, a[7][0] miss (0,7): a[7][0] hit, a[8][0] miss (0,8): a[8][0] hit, a[9][0] miss Με μπλε χρώμα υποδεικνύονται οι πρώτες επαναλήψεις που χρειάζονται για να καλύψουν χρονικά τη μεταφορά των δεδομένων που ζητά η prf. Αυτό σημαίνει ότι αν η prf κληθεί στην επανάληψη (0,0), τότε τα δεδομένα που ζήτησε θα έρθουν στην cache στην επανάληψη (0,7). Το ερώτημα που τίθεται επομένως τώρα, είναι, για ποιες αναφορές στοιχείων του πίνακα θα εφαρμόσουμε προφόρτωση; Η
απάντηση είναι, για τις αναφορές εκείνες οι οποίες καταλήγουν σε misses. Δηλαδή, όπως φαίνεται και από την παραπάνω ακολουθία, για το στοιχείο a[j+1][i]. Έτσι, όταν είμαστε στην επανάληψη (0,0), πρέπει να ζητήσουμε το a[8][0], όταν είμαστε στην (0,1) το a[9][0], κ.ο.κ. Ο κώδικας λοιπόν, χωρίς να λαμβάνουμε ειδική μέριμνα για τον πρόλογο και τον επίλογο του εσωτερικού loop, γίνεται ως εξής: for(i=0; i<4; i++) for(j=0; j<100; j++){ prf(&a[j+8][i]); result += a[j][i]*a[j+1][i] + 0.5; } Οι αντικαταστάσεις των least recently used blocks που γίνονταν στην περίπτωση του ερωτήματος α και οι οποίες οδηγούσαν εν τέλει σε capacity misses, γίνονται κι εδώ (πιο νωρίς μάλιστα, εξαιτίας των άχρηστων προφορτώσεων στις τελευταίες επαναλήψεις του εσωτερικού loop). Επομένως, σε κάθε επανάληψη του εξωτερικού loop, τα μόνα misses που έχουμε είναι τα 8 πρώτα που φαίνονται και στην παραπάνω ακολουθία, οπότε συνολικά θα έχουμε 32 misses. γ) Υποθέστε τώρα ότι έχετε μια cache ίδιας οργάνωσης, αλλά απείρου μεγέθους (δηλαδή, δεν υπάρχουν capacity misses). Πώς θα ξαναγράφατε τον κώδικα του ερωτήματος 2, μειώνοντας περαιτέρω τον αριθμό των κλήσεων στην prf; O κώδικας του ερωτήματος β, χωρίς επιπλέον εντολές και με λιγότερες κλήσεις στην prf, μπορεί να γραφτεί ως εξής: for(j=0; j<100; j++){ prf(&a[j+8][0]); result += a[j][0]*a[j+1][0] + 0.5; } for(i=1; i<4; i++) for(j=0; j<100; j++) result += a[j][i]*a[j+1][i] + 0.5; δ) Θεωρείστε πάλι τον αρχικό κώδικα (χωρίς τις προφορτώσεις, δηλαδή) και την αρχική cache της άσκησης. Εκτός από την προφόρτωση δεδομένων, ποια άλλη γνωστή τεχνική βελτιστοποίησης θα εφαρμόζατε στον κώδικα ώστε να μειωθούν τα misses; Ξαναγράψτε τον κώδικα εξηγώντας πώς επιτυγχάνεται η μείωση αυτή. Στο σώμα του loop δεν υπάρχουν εξαρτήσεις, επομένως μπορούμε να εφαρμόσουμε αναδιάταξη των βρόχων. for(j=0; j<100; j++) for(i=0; i<4; i++) result += a[j][i]*a[j+1][i] + 0.5; Τώρα ο πίνακας προσπελαύνεται όπως είναι αποθηκευμένος, δηλαδή κατά γραμμές. Αυτό έχει σαν αποτέλεσμα καλύτερη τοπικότητα στις προσπελάσεις δεδομένων, αφού γειτονικά μεταξύ τους στοιχεία προσπελαύνονται σε διαδοχικές επαναλήψεις του εσωτερικού loop. Έτσι, misses έχουμε μόνο όταν
αναφερόμαστε στο πρώτο στοιχείο κάθε γραμμής (i=0). Στις αναφορές που ακολουθούν και οι οποίες αφορούν τα επόμενα στοιχεία στην ίδια γραμμή, έχουμε hits, καθότι αυτά τα στοιχεία έχουν έρθει στην cache όταν έγινε η αναφορά στο πρώτο. ο Θέμα 3 (35%): Θεωρήστε ένα σύστημα μνήμης με μία cache χωρητικότητας 1 ΜΒ δεδομένων, με cache line 4 λέξεων. Το μέγεθος της λέξης είναι 32 bits. Η μικρότερη μονάδα δεδομένων που μπορεί να διευθυνσιοδοτηθεί είναι το 1 byte, ενώ οι διευθύνσεις μνήμης έχουν εύρος 32 bit. Για κάθε μία από τις ακόλουθες περιπτώσεις οργάνωσης της cache: (i) ευθείας αντιστοίχισης (direct mapped) (ii) συσχέτισης 2 δρόμων (2-way set associative) (iii) πλήρως συσχετιστική (fully associative) α) Υπολογίστε τον αριθμό των bits καθενός από τα επιμέρους πεδία στα οποία χωρίζεται μία διεύθυνση μνήμης σε μία τέτοια οργάνωση cache. Παρουσιάστε ένα διάγραμμα που να δείχνει πώς διαχωρίζεται η διεύθυνση στα πεδία αυτά, και εξηγείστε τη σημασία του καθενός. Διαθέτουμε cache χωρητικότητας 1 MB = 2 20 bytes δεδομένων. bits bytes 4 Κάθε cache line περιέχει 4 λέξεις = 4 words 32 = 4words 4 = 16bytes = 2 bytes. word word Εφόσον η δεικτοδότηση της cache line γίνεται ανά byte δεδομένων και κάθε cache line αποτελείται από 2 4 bytes, για τη δεικτοδότησή τους χρειάζονται 4 bits byte offset. 20 2 bytes 16 (i) Η cache αποτελείται από = 2 cache lines και επομένως για τη δεικτοδότησή τους 4 bytes 2 cache line χρειάζονται 16 bits index. Οι διευθύνσεις μνήμης έχουν εύρος 32bit, άρα το τμήμα tag της διεύθυνσης αποτελείται από: 32 16 4 = 12 bits tag. tag index offset 12 bits 16 bits 4 bits 16 (ii) Η cache αποτελείται από 2 cache lines, ενώ ανά δύο οι cache lines ανήκουν στο ίδιο set. Άρα η 2- way set associative cache αποτελείται από τους χρειάζονται 15 bits index. 16 2 cache lines 15 = 2 sets και επομένως για τη δεικτοδότησή cache lines 2 set Οι διευθύνσεις μνήμης έχουν εύρος 32bit, άρα το τμήμα tag της διεύθυνσης αποτελείται από: 32 15 4 = 13 bits tag.
tag index offset 13 bits 15 bits 4 bits (iii) Μία fully associative cache αποτελείται από 1 μόνο set και επομένως το τμήμα index της διεύθυνσης αποτελείται από 0 bits. Οι διευθύνσεις μνήμης έχουν εύρος 32bit, άρα το τμήμα tag της διεύθυνσης αποτελείται από: 32 0 4 = 28 bits tag. tag offset 28 bits 4 bits Συνοψίζοντας, το τμήμα index της διεύθυνσης εξυπηρετεί στη δεικτοδότηση των διαφορετικών sets της cache, για τον εντοπισμό της(των) cache line(s) στην(-ις) οποία(-ες) μπορεί να αποθηκευθεί το ζητούμενο δεδομένο. Το τμήμα offset της διεύθυνσης εξυπηρετεί στον εντοπισμό (ανάγνωση/εγγραφή) των μεμονομένων bytes τα οποία εμπεριέχονται σε κάθε cache line. Το τμήμα tag της διεύθυνσης αποθηκεύεται μαζί με τα δεδομένα της cache line, γιατί σε κάθε cache line μπορούν να αποθηκευθούν περισσότερα του ενός block μνήμης, ώστε να μπορούμε να τα διακρίνουμε μεταξύ τους. β) Παρουσιάστε σε block διάγραμμα την cache, τα επιμέρους πεδία μιας διεύθυνσης, και τον τρόπο που διασυνδέονται μεταξύ τους ώστε να οδηγήσουν στην προσπέλαση (εγγραφή/ανάγνωση) δεδομένων της cache. Eξηγείστε εν συντομία τη σημασία της κάθε διασύνδεσης στη διαδικασία εύρεσης δεδομένων στην cache. Λάβετε υπόψη ότι κάθε cache line έχει ένα επιπλέον bit valid/invalid. Τι ποσοστό του συνολικού μεγέθους της cache αφιερώνεται για τα bits του tag σε κάθε μία από τις περιπτώσεις; Στα παρακάτω διαγράμματα φαίνεται ο τρόπος προσπέλασης των δεδομένων της cache: Με βάση το τμήμα index της διεύθυνσης βρίσκουμε την cache line που μπορεί να αποθηκευθεί το δεδομένο που εμπεριέχεται στη δοσμένη διεύθυνση. Στη συνέχεια συγκρίνουμε το τμήμα tag της δοσμένης διεύθυνσης με το αποθηκευμένο στην cache line tag. Αν τα δύο tag είναι ίσα και τα δεδομένα που εμπεριέχονται στην cache είναι έγκυρα (valid), τότε πράγματι στην cache line που εξετάζουμε βρίσκεται το ζητούμενο block δεδομένων. Αν πρόκειται για ανάγνωση δεδομένων, με βάση το τμήμα offset της διεύθυνσης απομονώνουμε το ζητούμενο byte δεδομένων. Αν πρόκειται για εγγραφή, και πάλι με βάση το offset βρίσκουμε την ακριβή θέση και αποστέλλουμε το δεδομένο. (i) Κάθε cache line αποτελείται από 1 bit valid/invalid (για τον προσδιορισμό της εγκυρότητας των δεδομένων της), τα 12 bits του tag (για τη διάκριση μεταξύ των διαφορετικών block μνήμης που μπορούν να αποθηκευθούν στη συγκεκριμένη θέση της cache) και 4 λέξεις δεδομένων: 4 32bits = 128 bits δεδομένων. Σύνολο bits ανά cache line: 1+12+128 = 141 bits Άρα το ποσοστό των bits που αφιερώνονται στο tag επί του συνόλου των bits της cache είναι: 12 = 8,51% 141
tag index offset 12 bits 16 bits 4 bits V/I tag data 4 4 4 4 2 16 = MUX 16:1 (ii) Κάθε cache line αποτελείται από 1 bit valid/invalid, τα 13 bits του tag και 4 λέξεις δεδομένων: 4 32bits = 128 bits δεδομένων. Σύνολο bits ανά cache line: 1+13+128 = 142 bits Άρα το ποσοστό των bits που αφιερώνονται στο tag επί του συνόλου των bits της cache είναι: 13 = 9,15% 142 Σημειώνουμε ότι στην 2-way set associative cache ο έλεγχος των 2 tags γίνεται ταυτόχρονα, και με βάση ποιο συμφωνεί με το αντίστοιχο τμήμα της εξεταζόμενης διεύθυνσης, επιλέγονται τα ζητούμενα δεδομένα.
tag index offset 13 bits 15 bits 4 bits V/I tag data 4 4 4 4 2 15 sets = MUX 16:1 V/I tag data 4 4 4 4 2 15 sets = MUX 16:1 MUX 2:1 (iii) Κάθε cache line αποτελείται από 1 bit valid/invalid, τα 28 bits του tag και 4 λέξεις δεδομένων: 4 32bits = 128 bits δεδομένων.
Σύνολο bits ανά cache line: 1+28+128 = 156 bits Άρα το ποσοστό των bits που αφιερώνονται στο tag επί του συνόλου των bits της cache είναι: 28 = 17,83% 156 Σημειώνουμε ότι στην fully set associative cache ο έλεγχος όλων των tags γίνεται ταυτόχρονα, και με βάση ποιο συμφωνεί με το αντίστοιχο τμήμα της εξεταζόμενης διεύθυνσης (αν υπάρχει τέτοιο), επιλέγονται τα ζητούμενα δεδομένα. tag offset 18 bits 4 bits V/I tag data 4 4 4 4 2 16 cache lines = 2 16 συγκριτές MUX 16:1 16 2 2 16 2 16 πολυπλέκτες 2 16 MUX 2 16 :1 γ) Σε ποιες θέσεις της cache απεικονίζονται οι ακόλουθες διευθύνσεις bytes στη μνήμη (δίνονται σε 16- δική μορφή): 300Α21, 20FC03, 1B30, 1B32, 51028, A2E, 20FC05, 29E000, 200A22, 151023, 1B33, 300Α21, 251027, 1B33, 300A2D, 51025. Η αναζήτησή τους στη μνήμη καταλήγει σε miss ή hit; (i) Το τμήμα byte offset αποτελείται από 4bits, άρα ένα 16-δικό ψηφίο (το LSB 16-δικό ψηφίο της διεύθυνσης). Το τμήμα index αποτελείται από 16bits, άρα τέσσερα 16-δικά ψηφία (τα αμέσως γειτονικά 16-δικά ψηφία της διεύθυνσης).
(i) miss/hit index Tag (δυαδικά) σχόλια 1. 300Α21 miss 00Α2 3 2. 20FC03 miss 0FC0 2 3. 1B30 miss 1B3 0 4. 1B32 hit 1B3 0 (βλ. κλήση #3) 5. 51028 miss 5102 0 6. A2E miss 00A2 0 διαγραφή δεδομ.ένου #1 7. 20FC05 hit 0FC0 2 βλ. κλήση #2 8. 29E000 miss 9E00 2 9. 200A22 miss 00A2 2 διαγραφή δεδομ.ένου #6 10. 151023 miss 5102 1 διαγραφή δεδομ.ένου #5 11. 1B33 hit 1B3 0 βλ. Κλήση #4 12. 300Α21 miss 00Α2 3 διαγραφή δεδομ.ένου #9 13. 251027 miss 5102 2 διαγραφή δεδομ.ένου #10 14. 1B33 hit 1B3 0 (βλ. κλήση #11) 15. 300A2D hit 00A2 3 (βλ. κλήση #12) 16. 51025 miss 5102 0 διαγραφή δεδομ.ένου #13 (ii) Ομοίως, το τμήμα word offset αποτελείται από 4bits, άρα ένα 16-δικό ψηφίο (το LSB 16-δικό ψηφίο της διεύθυνσης). Το τμήμα index αποτελείται από 8bits, άρα δύο 16-δικά ψηφία (τα αμέσως γειτονικά 16-δικά ψηφία της διεύθυνσης). (ii) miss/hit index Tag σχόλια (δεκαεξαδικ) 1. 300Α21 miss 00Α2 6 1 ο στο set 2. 20FC03 miss 0FC0 4 3. 1B30 miss 1B3 0 4. 1B32 hit 1B3 0 βλ. κλήση #3 5. 51028 miss 5102 0 1 ο στο set 6. A2E miss 00A2 0 (2 ο στο set) 1 ο στο set 7. 20FC05 hit 0FC0 4 (βλ. κλήση #2) 8. 29E000 miss 1E00 5 9. 200A22 miss 00A2 4 (διαγραφή δεδομ.ένου #1 1 ο στο set) (2 ο στο set) 1 ο στο set 10. 151023 miss 5102 2 (2 ο στο set) 1 ο στο set 11. 1B33 hit 1B3 0 (βλ. κλήση #4) 12. 300Α21 miss 00Α2 6 (διαγραφή δεδομ.ένου #6 1 ο στο set) 2 ο στο set 13. 251027 miss 5102 4 (διαγραφή δεδομ.ένου 5-2 ο στο set) 1 ο στο set 14. 1B33 hit 1B3 0 (βλ. κλήση #11) 15. 300A2D hit 00A2 6 (βλ. κλήση #12 2 ο στο set) 16. 51025 miss 5102 0 (διαγραφή δεδομ.ένου #10-2 ο στο set) (iii)
Με τη συγκεκριμένη οργάνωση μνήμης τα δεδομένα μπορούν να τοποθετηθούν οπουδήποτε. Δεδομένου ότι η cache έχει πολύ μεγαλύτερη χωρητικότητα σε σχέση με τα 16 στοιχεία της εκφώνησης, miss θα είναι τα στοιχεία όταν καλείται για πρώτη φορά στην cache η cache line στην οποία ανήκουν. (iii) miss/hit Tag σχόλια (δεκαεξαδικ) 1. 300Α21 miss 300Α2 Compulsory miss 2. 20FC03 miss 20FC0 Compulsory miss 3. 1B30 miss 1B3 Compulsory miss 4. 1B32 hit 1B3 βλ. #3 5. 51028 miss 5102 Compulsory miss 6. A2E miss A2 Compulsory miss 7. 20FC05 hit 20FC0 βλ. #2 8. 29E000 miss 29E00 Compulsory miss 9. 200A22 miss 200A2 Compulsory miss 10. 151023 miss 15102 Compulsory miss 11. 1B33 hit 1B3 βλ. #4 12. 300Α21 hit 300Α2 βλ. #1 13. 251027 miss 25102 Compulsory miss 14. 1B33 hit 1B3 βλ. #11 15. 300A2D hit 300A2 βλ. #12 16. 51025 hit 5102 βλ. #5