Ο κλιμακωτός κώδικας βασίζεται σε σημαίες που θέτουν διάφορες εντολές για να κάνει διακλάδωση ως απάντηση σε μια συγκεκριμένη συνθήκη. Οι πακεταρισμένες τιμές, ωστόσο, δεν αντιπροσωπεύουν μία αλλά πολλές τιμές παράλληλα. Μια μόνο συνθήκη μπορεί να μην ισχύει για μια λωρίδα και να ισχύει για μια άλλη.
Γι' αυτό ο κώδικας SIMD είναι χωρίς διακλαδώσεις από προεπιλογή.
Αντί να βασίζονται σε σημαίες, οι πακεταρισμένες συγκρίσεις συνήθως παράγουν μια μάσκα στον τελεστέο προορισμού.
Για κάθε λωρίδα, η σύγκριση γεμίζει ολόκληρη τη λωρίδα με άσσους όταν είναι αληθής και με μηδενικά όταν είναι ψευδής.
Αν διαβαστεί ως προσημασμένος ακέραιος, μια αληθής λωρίδα είναι -1 και μια ψευδής λωρίδα είναι 0.
Αυτή η μάσκα μπορεί στη συνέχεια να συνδυαστεί με πράξεις σε επίπεδο bit για να φιλτράρει συγκεκριμένες λωρίδες.
Μια κλιμακωτή cmp είναι γενική με την έννοια ότι χρησιμοποιείται για να θέσει διάφορες σημαίες ταυτόχρονα.
Μια άλλη εντολή μπορεί στη συνέχεια να καταναλώσει αυτές τις σημαίες είτε για να κάνει διακλάδωση είτε για να εκτελέσει υπολογισμούς.
Ωστόσο, επειδή μια πακεταρισμένη σύγκριση και ελέγχει μια συνθήκη και υπολογίζει μια μάσκα, δεν είναι γενική. Η σύγκριση πρέπει να δεχτεί την ακριβή συνθήκη που ελέγχεται.
Υπάρχουν δύο τρόποι να γίνει αυτό:
eq για ισότητα και gt για «μεγαλύτερο από».
Οι άλλες παραλλαγές κατασκευάζονται συνδυάζοντας το αποτέλεσμα μιας από αυτές.Εκτός από τη χρήση μιας συγκεκριμένης κατάληξης συνθήκης στις συγκρίσεις ακεραίων, η σύνταξη ακολουθεί την ίδια δομή που έχουμε ήδη δει:
p + cmp + συνθήκη + μέγεθος (b, w, d ή q).cmp + p + μέγεθος (s ή d).
Η συνθήκη περνιέται σε ένα άμεσο ως πρόσθετος τελεστέος.Όπως αναφέρθηκε, υπάρχουν μόνο συγκρίσεις ακεραίων για ισότητα και για «μεγαλύτερο από»:
| εντολή | περιγραφή |
|---|---|
pcmpeqb, pcmpeqw, pcmpeqd, pcmpeqq
|
ισότητα ανά λωρίδα |
pcmpgtb, pcmpgtw, pcmpgtd, pcmpgtq
|
προσημασμένο «μεγαλύτερο από» ανά λωρίδα |
movdqa xmm0, [rel scores]
pcmpgtd xmm0, [rel threshold] ; lane i = 0xFFFFFFFF (-1) if scores[i] > threshold[i], else 0
Για να δημιουργήσεις μια σύγκριση «μικρότερο από», χρησιμοποίησε το gt με τους τελεστέους αντεστραμμένους: a < b == b > a.
Πρόσεξε ότι η σύγκριση είναι προσημασμένη. Για να κάνεις σύγκριση χωρίς πρόσημο, αντιστρέψε το πιο σημαντικό bit και των δύο τελεστέων. Αυτό μπορεί να γίνει με ένα XOR με μια μάσκα όπου μόνο το πιο σημαντικό bit έχει τεθεί.
Δύο βολικά ιδιώματα είναι:
Για παράδειγμα:
pxor xmm4, xmm4 ; xmm4 = all zeros
pcmpeqd xmm7, xmm7 ; xmm7 = all ones
Τα «όλα μηδενικά» και τα «όλα άσσους» είναι συνηθισμένες μάσκες για να κωδικοποιήσουν το "ψευδές παντού" και το "αληθές παντού", αντίστοιχα.
Μπορούν επίσης να χρησιμοποιηθούν για να αναπαραστήσουν πακεταρισμένο 0 ή πακεταρισμένο -1, που είναι συνηθισμένες τιμές φρουροί.
Για παράδειγμα, το NUL που σηματοδοτεί το τέλος μιας συμβολοσειράς είναι ένα 0.
Οι λωρίδες κινητής υποδιαστολής χρησιμοποιούν διαφορετική μορφή: μία εντολή, την cmpps (και την cmppd για λωρίδες 64 bit), με τη συνθήκη ως άμεσο:
movaps xmm0, [rel readings]
cmpps xmm0, [rel limits], 1 ; condition 1 is "less than": lane i = all ones if readings[i] < limits[i]
Ο NASM έχει επίσης ψευδοεντολές που αντιστοιχίζονται στο σωστό άμεσο και είναι πιο εύκολες στην απομνημόνευση.
Σε όλα τα παρακάτω, το x στο px μπορεί να είναι s (αριθμοί κινητής υποδιαστολής 32 bit) ή d (αριθμοί κινητής υποδιαστολής 64 bit):
| ψευδοεντολή | άμεσο | σύγκριση |
|---|---|---|
cmpeqpx |
0 | a == b |
cmpltpx |
1 | a < b |
cmplepx |
2 | a <= b |
cmpunordpx |
3 | το a είναι NaN ή το b είναι NaN |
cmpneqpx |
4 | a != b |
cmpnltpx |
5 | a >= b |
cmpnlepx |
6 | a > b |
cmpordpx |
7 | ούτε το a ούτε το b είναι NaN |
Μια μάσκα κωδικοποιεί το αποτέλεσμα μιας συνθήκης. Μπορεί στη συνέχεια να χρησιμοποιηθεί για να επιλέξεις, λωρίδα προς λωρίδα, ανάμεσα σε δύο σύνολα τιμών σύμφωνα με αυτή τη συνθήκη. Παίρνουμε τη λωρίδα από τη μια τιμή όπου η μάσκα είναι αληθής και από την άλλη όπου είναι ψευδής:
; result = (a AND mask) OR (b AND NOT mask)
movdqa xmm2, xmm0 ; xmm0 holds the mask, keep a copy
pand xmm2, xmm3 ; xmm2 = a AND mask: lanes of a where mask is true
pandn xmm0, xmm4 ; xmm0 = NOT mask AND b: lanes of b where mask is false
por xmm2, xmm0 ; combine the two halves
Πρόσεξε ότι η ασυμμετρία της pandn αποδίδει εδώ: η μάσκα βρίσκεται στον προορισμό, αρνείται και επιλέγει από το b με μία μόνο εντολή.
Αυτό το μοτίβο είναι η πακεταρισμένη μορφή της επιλογής χωρίς διακλαδώσεις.
Κάθε λωρίδα υπολογίζεται και μόνο η μάσκα αποφασίζει ποια τιμή επιβιώνει, χωρίς κανένα jcc πουθενά.
Υπάρχουν εντολές που εκτελούν ακριβώς αυτή την επιλογή απευθείας, διαβάζοντας ένα bit ανά στοιχείο από έναν καταχωρητή μάσκας. Ονομάζονται εντολές blend:
| εντολή | στοιχείο | πηγή μάσκας |
|---|---|---|
pblendvb |
byte | υπονοούμενο xmm0
|
blendvps |
λωρίδα 32 bit | υπονοούμενο xmm0
|
blendvpd |
λωρίδα 64 bit | υπονοούμενο xmm0
|
Πρόσεξε ότι η πρώτη εντολή ακολουθεί τη σύνταξη των ακεραίων, ενώ οι άλλες δύο ακολουθούν τη σύνταξη των αριθμών κινητής υποδιαστολής. Ωστόσο, επειδή αυτές οι εντολές απλώς επιλέγουν ακατέργαστα byte, οποιαδήποτε από αυτές μπορεί να χρησιμοποιηθεί και με ακεραίους και με αριθμούς κινητής υποδιαστολής.
Για κάθε στοιχείο, η blend κρατάει τον προορισμό όταν το πιο σημαντικό bit του αντίστοιχου στοιχείου της μάσκας είναι μηδενισμένο, και παίρνει την πηγή όταν αυτό έχει τεθεί.
Εξετάζεται μόνο αυτό το πιο σημαντικό bit, κάτι που μια μάσκα σύγκρισης ικανοποιεί, αφού οι λωρίδες της είναι όλες άσσους ή όλες μηδενικά.
Ο καταχωρητής μάσκας είναι πάντα ο xmm0, ο οποίος είναι υπονοούμενος:
movaps xmm0, [rel mask] ; the selecting mask must be in xmm0
movaps xmm1, [rel b] ; destination: kept where the mask bit is clear
blendvps xmm1, [rel a] ; source: taken where the mask bit is set
Είναι επίσης δυνατό να χρησιμοποιήσεις την pblendvb για να επιλέξεις λωρίδες από μια μάσκα σύγκρισης για οποιοδήποτε άλλο μέγεθος.
Επειδή όλα τα byte σε μια αληθή λωρίδα είναι όλα άσσους, η pblendvb τα επιλέγει όλα.
Όλες αυτές οι εντολές προσθέτουν ένα v μετά την πράξη που εκτελούν (blend).
Αυτό το v σημαίνει μεταβλητό, επειδή η επιλογή δεν είναι στατική: εξαρτάται από έναν καταχωρητή.
Υπάρχουν επίσης παραλλαγές χωρίς το v, που επιλέγουν σύμφωνα με ένα άμεσο.
Ακολουθούν το ίδιο μοτίβο, επιλέγοντας τη λωρίδα i αν το bit i του άμεσου έχει τεθεί.
Παρόλο που είναι ισχυρός, ο κώδικας SIMD στερείται μεγάλο μέρος της ευελιξίας του κλιμακωτού κώδικα. Σε πολλές περιπτώσεις, είναι απαραίτητο να μετακινηθείς από έναν πακεταρισμένο καταχωρητή πίσω στον κόσμο των κλιμακωτών εντολών.
Η οικογένεια εντολών movmsk λειτουργεί ως γέφυρα ανάμεσα στους δύο κόσμους.
Αυτές οι εντολές εξάγουν το πιο σημαντικό bit κάθε λωρίδας σε έναν καταχωρητή γενικού σκοπού:
| εντολή | συλλέγει | πλάτος αποτελέσματος |
|---|---|---|
pmovmskb |
το πιο σημαντικό bit καθενός από τα 16 byte | 16 bit |
movmskps |
το πιο σημαντικό bit καθενός από τα 4 dwords | 4 bit |
movmskpd |
το πιο σημαντικό bit καθενός από τα 2 qwords | 2 bit |
Αν χρησιμοποιηθεί μετά από μια σύγκριση, κάθε bit που έχει τεθεί αντιπροσωπεύει μια "αληθή" λωρίδα και κάθε bit που έχει μηδενιστεί, μια "ψευδή" λωρίδα.
Αυτό το αποτέλεσμα μπορεί στη συνέχεια να χειριστεί ως συνήθως με κλιμακωτές εντολές.
Για παράδειγμα, μια popcnt μετράει τον αριθμό των αντιστοιχιών και η tzcnt βρίσκει την πρώτη.
Ο καταχωρητής γενικού σκοπού μπορεί να είναι πλάτους 32 ή 64 bit.
Υπάρχει επίσης μια πακεταρισμένη παραλλαγή της κλιμακωτής εντολής test: η ptest.
Είναι παρόμοια με το κλιμακωτό αντίστοιχό της στο ότι εκτελεί μια πράξη AND ανάμεσα σε δύο τελεστέους, χωρίς να τους τροποποιεί.
Σε αντίθεση με την test, η ptest εκτελεί επίσης μια πράξη ANDN, αρνώντας τον πρώτο τελεστέο.
Έτσι, η ptest μπορεί να θεωρηθεί ως μια μη καταστρεπτική εκδοχή των pand και pandn που θέτει σημαίες σύμφωνα με το αποτέλεσμα.
Με τον ίδιο περίπου τρόπο όπως αυτές οι δύο εντολές, η ptest αντιμετωπίζει ολόκληρο τον καταχωρητή SIMD ως μία μόνο λωρίδα και έτσι δεν παίρνει πρόθεμα μεγέθους.
Αν το αποτέλεσμα μιας πράξης AND είναι 0, τίθεται η ZF, και αν η πράξη ANDN έχει ως αποτέλεσμα 0, αυτό που τίθεται είναι η CF.
Αυτό σημαίνει ότι η ptest μπορεί να χρησιμοποιηθεί για να ελέγξει και μια μάσκα «όλα άσσους» και μια μάσκα «όλα μηδενικά»:
ptest σε έναν καταχωρητή με τον εαυτό του θέτει τη ZF μόνο αν ο καταχωρητής είναι όλα μηδενικά.
Αυτό μιμείται το συνηθισμένο κλιμακωτό ιδίωμα της χρήσης της test σε έναν καταχωρητή με τον εαυτό του για να ελέγξεις το 0.ptest σε έναν καταχωρητή με μάσκα «όλα άσσους» θέτει τη CF μόνο αν ο καταχωρητής είναι όλα άσσους.
Επίσης, θέτει τη ZF μόνο αν ο καταχωρητής είναι όλα μηδενικά, κάνοντας δυνατό να ελέγξεις και τις δύο μάσκες ταυτόχρονα.pxor xmm0, xmm0 ; all zeros
pcmpeqb xmm1, xmm1 ; all ones
pcmpeqb xmm2, xmm2
ptest xmm0, xmm0 ; ZF set: a register against itself detects all zeros
ptest xmm0, xmm1 ; ZF set, CF clear: xmm0 is all zeros, not all ones
ptest xmm2, xmm1 ; CF is set only if xmm2 is all ones
Το αποτέλεσμα μιας ptest μπορεί να χρησιμοποιηθεί για διακλάδωση ή σε εντολές χωρίς διακλάδωση, όπως οι setcc ή cmovcc, ως συνήθως.
Διαχειρίζεσαι τον σταθμό βαθμολόγησης ενός σχολείου, βαθμολογώντας τα αποτελέσματα της τάξης ένα μπλοκ τη φορά.
Κάθε μπλοκ περιέχει 4 αποτελέσματα και ο σταθμός εφαρμόζει την ίδια πράξη σε κάθε αποτέλεσμα του μπλοκ. Μια βαθμολογία είναι ένας αριθμός κινητής υποδιαστολής 32 bit. Αρκετά βήματα δουλεύουν με μια μάσκα: ένα μπλοκ 4 λωρίδων όπου κάθε λωρίδα είναι είτε όλα άσσους (ένα ναι για αυτό το αποτέλεσμα) είτε όλα μηδενικά (ένα όχι).
Έχεις πέντε εργασίες. Τα τελούμενα τα λαμβάνεις μέσω διευθύνσεων μνήμης. Κάποιες εργασίες γράφουν την απάντησή τους σε μια διεύθυνση αποτελέσματος, ενώ άλλες την επιστρέφουν απευθείας.
Όλες οι διευθύνσεις μνήμης σε αυτή την άσκηση είναι ευθυγραμμισμένες στα 16 byte.
Οι υπολογισμοί σε αυτή την άσκηση θα πρέπει να γίνουν με εντολές SIMD.
Το πρώτο βήμα αξιολογεί κάθε αποτέλεσμα σε σχέση με ένα κατώφλι. Ένα αποτέλεσμα περνάει το όριο όταν η βαθμολογία του είναι αυστηρά μεγαλύτερη από το κατώφλι. Οποιαδήποτε βαθμολογία μικρότερη ή ίση με το κατώφλι δεν το περνάει.
Υλοποίησε τη συνάρτηση flag_above_threshold, η οποία φτιάχνει μια μάσκα με λωρίδα όλα άσσους για κάθε βαθμολογία πάνω από το κατώφλι της και λωρίδα όλα μηδενικά σε κάθε άλλη περίπτωση.
Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:
result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 λωρίδες της μάσκας.scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).thresholds: διεύθυνση μνήμης του κατωφλίου για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).scores = {72.0, 55.0, 90.0, 40.0}
thresholds = {60.0, 60.0, 60.0, 60.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}
Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.
Μια ξεχωριστή αναφορά αναδεικνύει τα τέλεια αποτελέσματα, εκείνα που έφτασαν τη μέγιστη δυνατή βαθμολογία.
Υλοποίησε τη συνάρτηση flag_perfect, η οποία φτιάχνει μια μάσκα με λωρίδα όλα άσσους για κάθε βαθμολογία ίση με τη μέγιστη τιμή της και λωρίδα όλα μηδενικά σε κάθε άλλη περίπτωση.
Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:
result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 λωρίδες της μάσκας.scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).maxima: διεύθυνση μνήμης της μέγιστης βαθμολογίας για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).scores = {100.0, 88.0, 100.0, 73.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}
Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.
Κάθε βαθμολογία κερδίζει μια κατάταξη από το 1 έως το 3:
50.0.Υλοποίησε τη συνάρτηση assign_ranks, η οποία γράφει την κατάταξη κάθε βαθμολογίας.
Θα πρέπει να ορίσεις το κατώφλι επιτυχίας και τις τιμές των κατατάξεων ως συσκευασμένες σταθερές στη μνήμη. Οι συναρτήσεις των δύο προηγούμενων εργασιών μπορούν να ξαναχρησιμοποιηθούν: μια βαθμολογία είναι τουλάχιστον κατάταξη 2 όταν είναι πάνω από το κατώφλι, και κατάταξη 3 όταν ισούται με τη μέγιστη.
Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:
result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 κατατάξεις, καθεμία ένας ακέραιος 32 bit χωρίς πρόσημο.scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).maxima: διεύθυνση μνήμης της μέγιστης βαθμολογίας για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).scores = {40.0, 75.0, 100.0, 60.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {1, 2, 3, 2}
Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.
Μέσα στη χρονιά, κάθε μαθητής συγκεντρώνει μια συνολική κατάταξη. Ο σταθμός καταμετρά πόσες κατατάξεις σε ολόκληρη την ομάδα πέφτουν κάτω από ένα κατώφλι επιτυχίας, για να προγραμματίσει πόσα επιπλέον τμήματα θα χρειαστούν.
Υλοποίησε τη συνάρτηση count_failures, η οποία επιστρέφει πόσες κατατάξεις, σε όλα τα μπλοκ, είναι αυστηρά κάτω από το κατώφλι επιτυχίας.
Το κατώφλι δίνεται ως ένα μπλοκ 4 ίδιων λωρίδων, ώστε να μπορείς να το φορτώσεις μία φορά και να το ξαναχρησιμοποιήσεις σε κάθε μπλοκ.
Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:
ranks: διεύθυνση μνήμης των κατατάξεων, ένας ακέραιος αριθμός μπλοκ 4 λωρίδων, όπου κάθε κατάταξη είναι ένας ακέραιος 32 bit χωρίς πρόσημο.block_count: ο αριθμός των μπλοκ 4 λωρίδων, πάντα μεγαλύτερος από 0.pass_threshold: διεύθυνση μνήμης του κατωφλίου επιτυχίας, με 4 ίδιους ακέραιους 32 bit.ranks = {1, 2, 3, 1, 2, 2, 1, 3} // 2 blocks
block_count = 2
pass_threshold = {2, 2, 2, 2}
// => 3
Αυτή η συνάρτηση επιστρέφει το πλήθος ως ακέραιο 32 bit με πρόσημο.
Πριν καταχωριστούν οι καταγραφές, ο σταθμός ελέγχει αν η ομάδα είναι καθαρή: περνάει όταν δεν απέτυχε ούτε ένα αποτέλεσμα σε κανένα μπλοκ.
Υλοποίησε τη συνάρτηση all_passed, η οποία επιστρέφει 1 αν πέρασαν όλοι οι μαθητές και 0 διαφορετικά.
Ένας μαθητής περνάει όταν η αντίστοιχη λωρίδα του στον πίνακα failing είναι όλα μηδενικά.
Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:
failing: διεύθυνση μνήμης των μασκών αποτυχίας, ένας ακέραιος αριθμός μπλοκ 4 λωρίδων, όπου κάθε λωρίδα είναι όλα άσσους ή όλα μηδενικά.block_count: ο αριθμός των μπλοκ 4 λωρίδων, πάντα μεγαλύτερος από 0.failing = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000} // 2 blocks
block_count = 2
// => 1
Αυτή η συνάρτηση επιστρέφει την απάντηση ως ακέραιο 32 bit με πρόσημο, είτε 1 είτε 0.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.