Αυτή τη φορά θέλω να μιλήσω για κάποιες μελλοντικές κατευθύνσεις για το διανυσματικό μοντέλο MRISC32. Για μια ανακεφαλαίωση, δείτε: The MRISC32 – Ένα διανυσματικό σχέδιο πρώτης CPU.
Το διανυσματικό μοντέλο που επιλέχθηκε για την αρχιτεκτονική του συνόλου εντολών MRISC32 έχει ήδη αποδειχθεί επιτυχημένο. Εφόσον έχει εφαρμοστεί στην CPU μεμονωμένης έκδοσης MRISC32-A1έχει γίνει σαφές ότι:
- Είναι πολύ εύκολο να εφαρμοστεί σε υλικό, ακόμη και σε αρχιτεκτονικές κατά παραγγελία με αποδοτική ενέργεια, και μπορεί να σχεδιαστεί για να χρησιμοποιεί ελάχιστους πρόσθετους πόρους, καθώς οι μονάδες εκτέλεσης κλιμακωτών μπορούν να χρησιμοποιηθούν τόσο για βαθμωτές όσο και για διανυσματικές λειτουργίες.
- Οι διανυσματικές λειτουργίες μπορούν να δώσουν σημαντικά κέρδη απόδοσης για μια CPU με τάξη, ειδικά σε τομείς όπως η πρόσβαση στη μνήμη και ο υπολογισμός κινητής υποδιαστολής όπου οι καθυστερήσεις είναι υψηλές (καθώς σχεδόν όλοι οι κίνδυνοι εξάρτησης δεδομένων εξαφανίζονται).
- Ως προγραμματιστής λογισμικού, είναι εύκολο να χρησιμοποιήσετε τη λειτουργικότητα. Συχνά είναι ως επί το πλείστον απλώς θέμα αντικατάστασης βαθμωτών καταχωρητών με καταχωρητές διανυσμάτων στον κώδικα συναρμολόγησης (καθώς οι περισσότερες εντολές λειτουργούν ακριβώς με τον ίδιο τρόπο σε βαθμωτή και διανυσματική λειτουργία).
Επιπλέον, αναμένεται ότι το ίδιο διανυσματικό μοντέλο θα είναι επιτυχές και σε εξαιρετικά παράλληλες αρχιτεκτονικές, καθώς:
- Η διανυσματική εκτέλεση μπορεί να είναι πλήρως παράλληλη (πλάτος μονάδας εκτέλεσης = διανυσματικός καταχωρητής με), πλήρως σειριακή (πλάτος μονάδας εκτέλεσης = 32 bit, πλάτος καταχωρητή διανυσμάτων = 2Ν * 32 bit), ή κάπου ενδιάμεσα – το μοντέλο λογισμικού είναι το ίδιο.
- Δεν υπάρχει όριο στο πόσο ευρείες διανυσματικές καταχωρήσεις και μονάδες εκτέλεσης μπορεί να υποστηρίξει το ISA – ακόμη και με δυαδική συμβατότητα λογισμικού με πιο στενές αρχιτεκτονικές χαμηλού επιπέδου.
Ωστόσο, κάποια λειτουργικότητα δεν έχει ακόμη οριστικοποιηθεί πραγματικά (κυρίως επειδή δεν ήμουν σίγουρος πώς να την εφαρμόσω σε υλικό), γεγονός που περιορίζει επί του παρόντος την αποτελεσματικότητα και τη χρησιμότητα των διανυσματικών λειτουργιών.
Επεξεργασία: Ορισμένα μέρη του ISA έχουν αλλάξει από τότε που γράφτηκε αυτό το άρθρο. Δείτε τα πιο πρόσφατα Εγχειρίδιο MRISC32 Instruction Set.
Το καλό
Πρόσφατα εφάρμοσα μια διανυσματική έκδοση του υπολογισμού το Σετ Mandelbrot. Δεδομένου ότι ο αλγόριθμος είναι βαρύς κινητής υποδιαστολής με πολλές εξαρτήσεις δεδομένων (κάθε τιμή σε μια επανάληψη εξαρτάται από την προηγούμενη τιμή), η βαθμωτή έκδοση οδήγησε σε πολλά στάδια αγωγών.
Στη διανυσματική έκδοση, τέσσερα γειτονικά pixel επεξεργάζονται παράλληλα. Ακολουθεί ένα απόσπασμα του βασικού βρόχου:
fmul v5, v3, v3 ; v5 = z.re^2
fmul v6, v4, v4 ; v6 = z.im^2
fadd v7, v5, v6 ; v7 = |z|^2
fslt v8, v7, r9 ; |z|^2 < 4 (r9)?
Όπως μπορείτε να δείτε, δύο από τις τέσσερις εντολές έχουν εξαρτήσεις από την προηγούμενη εντολή. Εάν επρόκειτο για βαθμωτές πράξεις (ενεργώντας σε καταχωρητές R αντί για καταχωρητές V), αυτές οι οδηγίες θα σταματούσαν για τρεις κύκλους η καθεμία (καθώς το fmul και το fadd έχουν καθυστέρηση τριών κύκλων).
Στη διανυσματική έκδοση, όλα τα stall εξαλείφονται, καθώς από τη στιγμή που ξεκινά μια διανυσματική εντολή και χρειάζεται το πρώτο διανυσματικό στοιχείο εισόδου, η προηγούμενη διανυσματική εντολή έχει ολοκληρώσει την εργασία στο πρώτο διανυσματικό στοιχείο (μόλις ξεκίνησε την εργασία στο τελευταίο διανυσματικό στοιχείο ). Έτσι ο αγωγός γεμίζει ανά πάσα στιγμή χωρίς φυσαλίδες.
Μέχρι εδώ καλά. Ωστόσο, ο αλγόριθμος Mandelbrot έχει ένα κριτήριο τερματισμού επανάληψης: εάν |z|2 ≥ 4 θα πρέπει να σταματήσουμε την επανάληψη. Η αντίστοιχη σύγκριση είναι η τελευταία οδηγία στο παραπάνω παράδειγμα.
Το κακό
Πρέπει να τερματίσουμε (υποκατάστημα) μία φορά όλα διανυσματικά στοιχεία ικανοποιούν τη συνθήκη. Πώς θα το κάνουμε αυτό; Δυστυχώς, αυτή τη στιγμή πρέπει να κάνουμε αυτό:
ldi vl, #2 ; Fold-length = 2
or/f v9, v8, v8 ; Folding OR
ldi vl, #1 ; Fold-length = 1
or/f v9, v9, v9 ; Folding OR
stw v9, r10, #0 ; Store 1 vector element on the stack
ldw r11, r10, #0 ; Load value into a scalar register
ldi vl, #4 ; Restore vector length = 4
bz r11, done ; Done if all elements are false
Στον παραπάνω κώδικα, οι τέσσερις πρώτες εντολές διπλώνουν τα αποτελέσματα των τεσσάρων διανυσματικών στοιχείων σε ένα μόνο στοιχείο, σε δύο βήματα όπως αυτό:

Τώρα V9[0] == FALSE μόνο αν όλα τα στοιχεία του V8 ήταν FALSE.
Οι επόμενες τέσσερις εντολές μεταφέρουν το διανυσματικό στοιχείο V9[0] σε έναν βαθμωτό καταχωρητή (R11), μέσω της στοίβας, και χρησιμοποιεί αυτόν τον βαθμωτό καταχωρητή για να εκτελέσει τη διακλάδωση υπό όρους (αυτό συμβαίνει επειδή οι εντολές διακλάδωσης μπορούν να δράσουν μόνο σε βαθμωτούς καταχωρητές).
Βελτιώσεις
Ακολουθούν ορισμένες προσθήκες στον ISA που θα βελτίωναν σημαντικά την κατάσταση (όλα αυτά έχουν προγραμματιστεί, αλλά δεν έχουν ακόμη εφαρμοστεί)…
Μεταφορά διανυσματικού στοιχείου σε βαθμωτό καταχωρητή
Μια προφανής έλλειψη είναι ότι πρέπει να μεταφέρουμε δεδομένα από έναν διανυσματικό καταχωρητή σε έναν βαθμωτό καταχωρητή μέσω μνήμης (store + load). Μια απλή προσθήκη στο σύνολο εντολών MRISC32 είναι μια εντολή που μετακινεί ένα μόνο διανυσματικό στοιχείο σε έναν βαθμωτό καταχωρητή. Τότε θα παίρναμε:
ldi vl, #2 ; Fold-length = 2
or/f v9, v8, v8 ; Folding OR
ldi vl, #1 ; Fold-length = 1
or/f v9, v9, v9 ; Folding OR
ldi vl, #4 ; Restore vector length = 4
vmov r11, v9, #0 ; Move v9[0] to r11
bz r11, done ; Done if all elements are false
Αυτή η επιπλέον οδηγία είναι αρκετά αδιαμφισβήτητη και θα απαιτούσε μόνο ελάχιστες αλλαγές στην εφαρμογή MRISC32-A1.
Προσθέστε μια κατάσταση μήκους σε διανυσματικούς καταχωρητές
Εάν προσθέσουμε μια κατάσταση μήκους διανύσματος σε κάθε καταχωρητή διανυσμάτων, δεν χρειάζεται να ορίσουμε ρητά τον καταχωρητή VL όταν κάνουμε τις λειτουργίες αναδίπλωσης.
Η κατάσταση μήκους του διανύσματος θα ενημερώνεται κάθε φορά που γράφεται ένας διανυσματικός καταχωρητής και το μήκος μιας διανυσματικής πράξης θα είναι η κατάσταση μήκους του πρώτου τελεστή πηγής. Στην περίπτωση αναδίπλωσης, το μήκος λειτουργίας θα είναι το μισό του μήκους του πρώτου τελεστή πηγής. Ο καταχωρητής VL θα χρησιμοποιηθεί μόνο για τις λίγες λειτουργίες που αρχικοποιούν έναν διανυσματικό καταχωρητή (π.χ. φορτία μνήμης και λειτουργίες που περιλαμβάνουν τον καταχωρητή VZ).
Τώρα ο κώδικας θα απλοποιηθεί σε:
or/f v9, v8, v8 ; Folding OR
or/f v9, v9, v9 ; Folding OR
vmov r11, v9, #0 ; Move v9[0] to r11
bz r11, done ; Done if all elements are false
Αυτή η αλλαγή είναι πιο περίπλοκη. Το κόλπο είναι ότι ουσιαστικά πρέπει να διαβάσετε την ιδιότητα διανυσματικού μήκους πριν εγγραφή fetch, αλλά είναι σίγουρα εφικτό.
Προσθέστε μια κατάσταση „σύνολο bit“ στους διανυσματικούς καταχωρητές
Θα μπορούσαμε να προσθέσουμε μια επιπλέον κατάσταση σε κάθε καταχωρητή διανυσμάτων που έχει τρεις πιθανές τιμές:
- -1 – Όλα τα bit όλων των διανυσματικών στοιχείων έχουν οριστεί (1).
- 0 – Όλα τα bit όλων των διανυσματικών στοιχείων διαγράφονται (0).
- +1 – Τουλάχιστον ένα bit από οποιοδήποτε διανυσματικό στοιχείο έχει οριστεί (1) και τουλάχιστον ένα bit από οποιοδήποτε διανυσματικό στοιχείο διαγράφεται (0).
Θα μπορούσαμε να προσθέσουμε μια εντολή για να ρωτήσουμε την κατάσταση του συνόλου bit ενός καταχωρητή διανυσμάτων και να τον μεταφέρουμε σε έναν βαθμωτό καταχωρητή. Εάν επεκτείνουμε την προγραμματισμένη εντολή „VMOV“ έτσι ώστε ο δείκτης στοιχείου -1 να αντιπροσωπεύει την κατάσταση του συνόλου bit, λαμβάνουμε τον ακόλουθο κώδικα:
vmov r11, v8, #-1 ; Get bit set state of v8
bz r11, done ; Done if all elements are false
Η προσθήκη της κατάστασης του συνόλου bit σε κάθε διανυσματικό καταχωρητή δεν είναι τετριμμένη (αλλά εξακολουθεί να είναι εφικτή). Ουσιαστικά χρειάζεται να προσθέσουμε ένα επιπλέον στάδιο μετά την παραγωγή ενός αποτελέσματος (από οποιαδήποτε διοχέτευση, ALU, FPU, μνήμη,…) που παρακολουθεί όλες τις τιμές διανυσματικών στοιχείων που παράγονται. Αυτό μπορεί να προσθέσει πολυπλοκότητα στην προώθηση τελεστών κ.λπ. και πιθανότατα απαιτεί έναν κύκλο καθυστέρησης (η κατάσταση θα ενημερωθεί μετά έχουν παραχθεί όλα τα διανυσματικά στοιχεία).
Ενεργοποίηση καταχωρητών διανυσμάτων ως συνθήκες διακλάδωσης
Με την κατάσταση συνόλου bit των καταχωρητών διανυσμάτων, μπορούμε να καταστήσουμε δυνατή τη χρήση ενός διανυσματικού καταχωρητή ως συνθήκη διακλάδωσης. Π.χ. χρησιμοποιώντας έναν διανυσματικό καταχωρητή ως συνθήκη διακλάδωσης θα διαβάσει απλώς την κατάσταση του συνόλου bit από τον αντίστοιχο καταχωρητή και θα ενεργήσει σε αυτό. Εάν ναι, θα λάβουμε τις ακόλουθες χρήσιμες οδηγίες υποκαταστήματος:
Εντολή | Εννοια |
---|---|
bz v1, foo | Διακλάδωση εάν όλα τα στοιχεία του v1 είναι ψευδή |
bs v1, foo | Διακλαδώστε εάν όλα τα στοιχεία του v1 είναι αληθή |
bnz v1, foo | Διακλαδώστε εάν τουλάχιστον ένα στοιχείο του v1 είναι αληθές |
bns v1, foo | Διακλαδώστε εάν τουλάχιστον ένα στοιχείο του v1 είναι ψευδές |
Με αυτήν τη λειτουργία, θα μπορούσαμε να μειώσουμε τις οκτώ οδηγίες στον ακόλουθο συμπαγή κώδικα:
bz v8, done ; Done if all elements are false
Αυτή η λειτουργία θα ήταν αρκετά απλή να προστεθεί στο υλικό και στο σύνολο εντολών. Ουσιαστικά θα χρειαστεί να θυσιάσουμε μόνο ένα bit απόστασης διακλάδωσης υπό όρους (προς το παρόν έχουμε 21 bit για την απόσταση, που είναι πολύ περισσότερα από ό,τι συνήθως χρειάζεστε για μια υπό όρους διακλάδωση). Θα πρέπει επίσης να προσθέσουμε κάποια επιπλέον λογική αποκωδικοποίησης, αλλά αυτή θα πρέπει να είναι αμελητέα.
Κάτι ακόμα: Καταστήματα με μάσκες
Ένα άλλο πράγμα που εμποδίζει ορισμένους αλγόριθμους από το να διανυσματοποιηθούν πλήρως με το τρέχον MRISC32 ISA είναι η έλλειψη λειτουργιών αποθήκευσης κρυφής μνήμης.
Πολλές λειτουργίες μπορούν ήδη να εκτελεστούν υπό όρους εντελώς φυσικά, π.χ. χρησιμοποιώντας την εντολή SEL που παρουσιάστηκε στις κινήσεις υπό όρους MRISC32 ή χρησιμοποιώντας άλλες λειτουργίες λογικής/επιλογής (π.χ. AND, ή MIN/MAX, κ.λπ.). Ωστόσο, η υπό όρους αποθήκευση μερικών επιλεγμένων διανυσματικών στοιχείων στη μνήμη δεν γίνεται εύκολα χωρίς αποκλειστικές οδηγίες.
Ένα παράδειγμα είναι το view frustum cullling σε γραφικά υπολογιστή: Μην σχεδιάζετε πρωτόγονα ή εικονοστοιχεία που βρίσκονται εκτός προβολής. Για παράδειγμα, μπορείτε να χρησιμοποιήσετε συντεταγμένες 2D ή 3D για να υπολογίσετε την τοποθεσία ενός pixel στην οθόνη και επιπλέον να υπολογίσετε την αντίστοιχη διεύθυνση μνήμης αυτού του pixel. Εάν ένα διανυσματικό στοιχείο αντιπροσωπεύει ένα εικονοστοιχείο εκτός οθόνης, αυτή η λειτουργία αποθήκευσης μνήμης δεν πρέπει να εκτελεστεί.
Μια λύση θα μπορούσε να είναι να συνδέσετε έναν από τους καταχωρητές διανυσμάτων (π.χ. V31) σε καλυμμένες λειτουργίες και να το ονομάσετε VM (για «μάσκα διανύσματος»). Επιπλέον, μπορούμε να προσθέσουμε μία ή περισσότερες ειδικές οδηγίες που χρησιμοποιούν σιωπηρά τον καταχωρητή VM ως μάσκα.
Για παράδειγμα, μια συγκαλυμμένη εντολή „store word“ για λειτουργία scatter θα μπορούσε να έχει την ακόλουθη υπογραφή:
stwm v1, r1, v2 ; v1[i] => MEM[r1 + v2[i]], if vm[i] is true
Μερικές ανοιχτές ερωτήσεις είναι:
- Ποιες λειτουργίες καταστήματος θα πρέπει να έχουν καλυμμένες εκδόσεις (λέξη, μισή λέξη, byte, συλλογή/σκέδαση, βάσει διασκελισμού);
- Πρέπει ο καταχωρητής VM να αντιμετωπίζεται ως καθαρά boolean μάσκα ή ως μάσκα επιλογής byte; Το τελευταίο θα επέτρεπε επίσης την απόκρυψη συσκευασμένων τύπων δεδομένων (π.χ. σε συνδυασμό με μια εντολή SEQ.B).
- Θα έπρεπε και άλλοι τύποι οδηγιών να έχουν καλυμμένες παραλλαγές; Π.χ. τα καλυμμένα φορτία μπορεί να είναι χρήσιμα, ειδικά σε ένα μηχάνημα με MMU (δεν έχει ακόμη εφαρμοστεί στο MRISC32) με πιθανά σφάλματα σελίδας για μη έγκυρες διευθύνσεις.
Από πλευράς εφαρμογής, τα σχετικά bits του καταχωρητή VM θα μπορούσαν να διατηρηθούν σε έναν αποκλειστικό καταχωρητή κατοπτρισμού, έτσι ώστε να μην χρειάζεται να σπαταλάμε μια γενική θύρα ανάγνωσης καταχωρητή διανυσμάτων για καλυμμένες λειτουργίες.
Αυτά για αυτή τη φορά!