Διαδρομές
/
x86-64 Assembly
x86-64 Assembly
/
Ασκήσεις
/
Γραμμή παραγωγής
Γραμμή παραγωγής

Γραμμή παραγωγής

Άσκηση εκμάθησης

Εισαγωγή

SIMD

Μία εντολή, πολλά δεδομένα (SIMD) είναι μια μορφή παραλληλισμού σε επίπεδο υλικού. Ο επεξεργαστής εκτελεί τον ίδιο υπολογισμό πάνω σε πολλά στοιχεία ταυτόχρονα.

Πάρε για παράδειγμα την άθροιση δύο πινάκων με τέσσερις αριθμούς κινητής υποδιαστολής 32 bit, στοιχείο προς στοιχείο. Μια βαθμωτή υλοποίηση θα χρησιμοποιούσε τέσσερις ξεχωριστές εντολές addss, μία για κάθε στοιχείο. Μια υλοποίηση SIMD χρησιμοποιεί μία μόνο addps για να κάνει και τις τέσσερις προσθέσεις παράλληλα. Έτσι, κάθε εντολή κάνει τετραπλάσια δουλειά.

Στη συμβολική γλώσσα x86-64, οι εντολές SIMD εκτελούνται συνήθως με καταχωρητές xmm. Κάποιες από αυτές τις εντολές λειτουργούν σε ακέραιους αριθμούς. Άλλες λειτουργούν σε αριθμούς κινητής υποδιαστολής.

Αυτοί οι ακέραιοι ή οι αριθμοί κινητής υποδιαστολής ονομάζονται συσκευασμένοι. Καθένας καταλαμβάνει έναν χώρο, που ονομάζεται λωρίδα, μέσα στον ίδιο καταχωρητή. Οι εντολές SIMD συνήθως λειτουργούν παράλληλα σε τιμές στην ίδια θέση λωρίδας σε δύο τελεστέους πηγής. Το αποτέλεσμα κάθε πράξης αποθηκεύεται στη λωρίδα στην ίδια θέση του τελεστέου προορισμού.

Μια λωρίδα δεν είναι ένα αυστηρό όριο. Είναι μια ερμηνεία που δίνει κάθε εντολή σε μια ακολουθία byte. Ένας τελεστέος 128 bit περιέχει 16 byte. Ανάλογα με την εντολή, αυτά τα byte μπορούν να ερμηνευτούν ως 16 λωρίδες 1 byte, 8 λωρίδες 2 byte, 4 λωρίδες 4 byte, 2 λωρίδες 8 byte, ή ακόμα και ως μία λωρίδα 16 byte.

Για παράδειγμα, η εντολή addps προσθέτει συσκευασμένες τιμές κινητής υποδιαστολής απλής ακρίβειας (4 byte). Για αυτή την εντολή, κάθε τμήμα των 4 byte σε έναν τελεστέο είναι μια διαφορετική λωρίδα. Σε έναν τελεστέο πλάτους 128 bit (16 byte):

τελεστέος 4η λωρίδα 3η λωρίδα 2η λωρίδα 1η λωρίδα
πηγή 1 1.5 2.1 6.2 3.4
+ + + + +
πηγή 2 0.5 8.26 -13.4 22.9
= = = = =
αποτέλεσμα 2.0 10.36 -7.2 26.3

Από την άλλη, ένας τελεστέος 128 bit θα ερμηνευόταν ως έχων 2 λωρίδες στην περίπτωση της mulpd. Αυτό συμβαίνει επειδή αυτή η εντολή εκτελεί συσκευασμένο πολλαπλασιασμό σε τιμές διπλής ακρίβειας (8 byte):

τελεστέος 2η λωρίδα 1η λωρίδα
πηγή 1 4.2 2.1
* * *
πηγή 2 1.0 92.4
= = =
αποτέλεσμα 4.2 194.04

Σε αυτή την ενότητα θα επικεντρωθούμε σε μια μικρή επιλογή εντολών SIMD. Αυτές λειτουργούν σε συσκευασμένες τιμές κινητής υποδιαστολής χρησιμοποιώντας καταχωρητές xmm.

Εντολές SIMD κινητής υποδιαστολής

Κατά κανόνα, οι εντολές SIMD που λειτουργούν σε τιμές κινητής υποδιαστολής έχουν μορφή δύο τελεστέων. Το όνομά τους ακολουθεί μια συγκεκριμένη δομή:

  1. Η πράξη που εκτελείται, με το ίδιο όνομα όπως το μη-SIMD (επίσης λεγόμενο βαθμωτό) αντίστοιχό της (π.χ. add, mul, κ.λπ.).
  2. Ένα επίθημα p που δηλώνει ότι η εντολή λειτουργεί σε συσκευασμένα δεδομένα, στην ίδια θέση που προηγουμένως καταλάμβανε ένα s (για βαθμωτά δεδομένα).
  3. Ένα επίθημα που δηλώνει το μέγεθος κάθε λωρίδας, ακολουθώντας την ίδια σημασιολογία με το μη-SIMD αντίστοιχό της (s για απλή ακρίβεια και d για διπλή ακρίβεια).

Το συσκευασμένο όνομα προκύπτει επομένως από το βαθμωτό όνομα, αντικαθιστώντας το s (βαθμωτό) στη μέση του μνημονικού με ένα p (συσκευασμένο). Το όνομα της πράξης και το επίθημα μεγέθους μένουν αμετάβλητα:

βαθμωτή συσκευασμένη σημασία
addss addps πρόσθεση, απλής ακρίβειας (λωρίδες 32 bit)
addsd addpd πρόσθεση, διπλής ακρίβειας (λωρίδες 64 bit)
mulss mulps πολλαπλασιασμός, απλής ακρίβειας
mulsd mulpd πολλαπλασιασμός, διπλής ακρίβειας
Αριθμητικές πράξεις

Οι αριθμητικές εντολές SIMD ακολουθούν τον παραπάνω κανόνα. Μερικά παραδείγματα:

addps xmm0, xmm1 ; xmm0 = xmm0 + xmm1 (each 32-bit float at the same position)
subpd xmm2, xmm3 ; xmm2 = xmm2 - xmm3 (each 64-bit float at the same position)
mulps xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (each 32-bit float at the same position)
divpd xmm6, xmm7 ; xmm6 = xmm6 / xmm7 (each 64-bit float at the same position)
Μετατροπή μεταξύ ακριβειών

Υπάρχουν επίσης εντολές SIMD για τη μετατροπή μεταξύ συσκευασμένων τιμών κινητής υποδιαστολής απλής και διπλής ακρίβειας. Ακολουθούν τον ίδιο κανόνα ονοματοδοσίας, με κάθε s για βαθμωτά να αντικαθίσταται από ένα p για συσκευασμένα:

βαθμωτή συσκευασμένη σημασία
cvtss2sd cvtps2pd μετατροπή από απλή ακρίβεια σε διπλή ακρίβεια
cvtsd2ss cvtpd2ps μετατροπή από διπλή ακρίβεια σε απλή ακρίβεια

Αυτές οι εντολές δείχνουν το προηγούμενο σημείο, ότι δηλαδή μια λωρίδα είναι ερμηνεία, όχι αυστηρό όριο. Οι τελεστέοι πηγής και προορισμού έχουν διαφορετικά μεγέθη λωρίδας, άρα και διαφορετικό αριθμό λωρίδων.

Για την cvtps2pd, η πηγή (ps) έχει 4 λωρίδες απλής ακρίβειας. Ο προορισμός (pd) έχει 2 λωρίδες διπλής ακρίβειας. Διαβάζονται μόνο οι 2 χαμηλότερες λωρίδες της πηγής. Οι 2 υψηλότερες αγνοούνται:

τελεστέος 4η λωρίδα 3η λωρίδα 2η λωρίδα 1η λωρίδα
πηγή ως ps αγνοείται αγνοείται 2.5 1.5
προορισμός ως pd 2.5 1.5

Για την cvtpd2ps, η πηγή (pd) έχει 2 λωρίδες διπλής ακρίβειας. Ο προορισμός (ps) έχει 4 λωρίδες απλής ακρίβειας. Και οι δύο λωρίδες της πηγής μετατρέπονται και γράφονται στις 2 χαμηλότερες λωρίδες του προορισμού. Οι 2 υψηλότερες λωρίδες του προορισμού μηδενίζονται:

τελεστέος 4η λωρίδα 3η λωρίδα 2η λωρίδα 1η λωρίδα
πηγή ως pd 4.0 3.0
προορισμός ως ps 0 0 4.0 3.0

Μερικά παραδείγματα:

cvtps2pd xmm0, xmm1 ; xmm0 = xmm1 as 2 doubles (low 2 lanes of xmm1 are read)
cvtpd2ps xmm0, xmm1 ; xmm0 = xmm1 as 2 floats (in low 2 lanes; upper 2 zeroed)
Τελεστέοι μνήμης

Οι παραπάνω εντολές SIMD έχουν παρουσιαστεί όλες με τελεστέους από καταχωρητή σε καταχωρητή. Μπορούν επίσης να δεχτούν έναν τελεστέο μνήμης ως πηγή (αλλά όχι ως προορισμό).

Ο αριθμός των byte που διαβάζονται από τη μνήμη καθορίζεται από την εντολή. Για παράδειγμα, η addps διαβάζει 16 byte (4 αριθμούς κινητής υποδιαστολής απλής ακρίβειας). Η cvtps2pd, αντίθετα, διαβάζει μόνο 8 byte (2 αριθμούς κινητής υποδιαστολής απλής ακρίβειας), επειδή αυτά είναι το μόνο που χρειάζεται ως είσοδο.

addps    xmm0, [rsi]      ; xmm0 = xmm0 + 4 floats at [rsi]
mulpd    xmm1, [rsi + 16] ; xmm1 = xmm1 * 2 doubles at [rsi + 16]
cvtps2pd xmm2, [rsi + 8]  ; xmm2 = (2 floats at [rsi + 8]) converted to 2 doubles

Όταν ο τελεστέος μνήμης έχει πλάτος 16 byte, πρέπει να είναι ευθυγραμμισμένος στα 16 byte. Μια ευθυγραμμισμένη θέση μνήμης είναι εκείνη της οποίας η διεύθυνση είναι πολλαπλάσιο του μεγέθους της πράξης (16 byte σε αυτή την περίπτωση).

Για να δηλώσεις ευθυγραμμισμένη μνήμη, μπορείς να χρησιμοποιήσεις την οδηγία align στα τμήματα .data και .rodata, ή την alignb, στο τμήμα .bss:

section .rodata
align 16
mem1: dd 4.0, 3.5, 2.0, -1.0

section .data
align 16
mem2: dq 6.0, 2.1

section .bss
alignb 16
mem3: resb 16

section .text
fn:
    movaps xmm0, oword [rel mem1] ; loads 4 packed 32-bit floats in xmm0
    movapd xmm1, oword [rel mem2] ; loads 2 packed 64-bit floats in xmm1
    movaps oword [rel mem3], xmm0 ; stores 4 packed 32-bit floats in mem3
    ret

Οι τελεστέοι μνήμης μικρότεροι από 16 byte, όπως η πηγή των 8 byte της cvtps2pd, δεν έχουν απαίτηση ευθυγράμμισης.

Φόρτωση/Αποθήκευση τιμών

Υπάρχουν τέσσερις εντολές που αντιγράφουν 128 bit συσκευασμένων τιμών κινητής υποδιαστολής. Λειτουργούν μεταξύ δύο καταχωρητών xmm, ή μεταξύ ενός καταχωρητή xmm και μιας θέσης μνήμης:

εντολή περιγραφή
movaps αντιγράφει συσκευασμένα δεδομένα απλής ακρίβειας από, ή προς, μια ευθυγραμμισμένη θέση μνήμης
movups αντιγράφει συσκευασμένα δεδομένα απλής ακρίβειας από, ή προς, μια μη ευθυγραμμισμένη θέση μνήμης
movapd αντιγράφει συσκευασμένα δεδομένα διπλής ακρίβειας από, ή προς, μια ευθυγραμμισμένη θέση μνήμης
movupd αντιγράφει συσκευασμένα δεδομένα διπλής ακρίβειας από, ή προς, μια μη ευθυγραμμισμένη θέση μνήμης

Όλες αυτές οι εντολές μπορούν να χρησιμοποιηθούν για την αντιγραφή τιμών μεταξύ δύο καταχωρητών xmm.

Αν η πράξη περιλαμβάνει μια θέση μνήμης, οι movaps και movapd απαιτούν να είναι ευθυγραμμισμένη. Οι movaps και movapd θα προκαλέσουν σφάλμα αν τους δοθεί μη ευθυγραμμισμένη διεύθυνση. Λειτουργούν ταυτόχρονα και ως έλεγχος ασφαλείας ότι η διάταξη των δεδομένων σου είναι αυτή που νομίζεις.

Από την άλλη, οι movups και movupd μπορούν να χρησιμοποιηθούν με οποιαδήποτε θέση μνήμης, ευθυγραμμισμένη ή μη.

Το πρόθεμα μεγέθους για μια τιμή 16 byte στη NASM είναι oword.

Επεκτάσεις SIMD

Το SIMD αποτελεί αντικείμενο ενεργού ανάπτυξης στο οικοσύστημα x86-64 εδώ και δεκαετίες. Κάθε επέκταση της αρχιτεκτονικής φέρνει νέες εντολές ή νέες χρήσεις για παλιές εντολές.

Συγκεκριμένα, οι επεκτάσεις AVX και AVX2 εισάγουν κάποιες σημαντικές αλλαγές.

Πρώτον, υποστήριξη για καταχωρητές SIMD 256 bit, που ονομάζονται ymm. Επεκτείνουν τους αντίστοιχους καταχωρητές xmm, περίπου όπως ο rax περιέχει τον eax.

Δεύτερον, περιλαμβάνουν υποστήριξη για παραλλαγές 3 τελεστέων για τις περισσότερες εντολές που λειτουργούν σε καταχωρητές xmm (και ymm). Αυτό περιλαμβάνει όχι μόνο εντολές SIMD, αλλά και όσες λειτουργούν σε βαθμωτές τιμές κινητής υποδιαστολής. Αυτές οι παραλλαγές ακολουθούν τον ίδιο συμβολισμό με το αντίστοιχο SSE, με ένα v μπροστά:

vaddsd xmm0, xmm1, xmm2   ; xmm0 = xmm1 + xmm2 (scalar sum of 64-bit floats)
vmulps xmm1, xmm3, xmm4   ; xmm1 = xmm3 * xmm4 (packed multiplication of 32-bit floats)
vmovupd ymm5, yword [mem] ; this copies 32 bytes from the location at mem to ymm5

Τρίτον, η απαιτούμενη ευθυγράμμιση των τελεστέων μνήμης SIMD χαλαρώνει. Μόνο οι ρητά ευθυγραμμισμένες μεταφορές, όπως η vmovaps, την απαιτούν.

Σε αυτό το track, έχουμε περιοριστεί σε εντολές και καταχωρητές μέχρι τις προηγούμενες επεκτάσεις SSE. Αυτό αρκεί για να διδάξει τα θεμέλια και εξασφαλίζει συμβατότητα με ένα ευρύ φάσμα επεξεργαστών.

Ωστόσο, ο test-runner υποστηρίζει πλήρως τα AVX και AVX2, ακόμα και τις νεότερες επεκτάσεις που αποτελούν μέρος του AVX-512. Είσαι ελεύθερος να τα χρησιμοποιήσεις.

Note

Καλή πρακτική είναι να είσαι συνεπής στη χρήση των παραλλαγών SIMD. Προσπάθησε να μην ανακατεύεις παραλλαγές SSE και AVX για τις ίδιες εντολές.

Αν ο κώδικάς σου ανακατεύει αυτές τις παραλλαγές, καλή πρακτική είναι να χρησιμοποιείς vzeroupper αφού τελειώσεις τη δουλειά σου με AVX και πριν χρησιμοποιήσεις SSE. Αυτό σπάει τις αλυσίδες εξαρτήσεων και αποφεύγει μια δαπανηρή καθυστέρηση όταν ο επεξεργαστής περνάει μεταξύ κατάστασης AVX και SSE.

Οδηγίες

Γράφεις κώδικα για το σύστημα δεδομένων στο δάπεδο παραγωγής ενός εργοστασίου ανταλλακτικών αυτοκινήτων. Εξαρτήματα κινητήρα και πλαισίου περνούν από μια σειρά σταθμών κατεργασίας. Κάθε σταθμός παράγει μετρήσεις που πρέπει να συνδυαστούν, να συγκριθούν, να βαθμονομηθούν ή να κανονικοποιηθούν πριν εκτελεστεί το επόμενο στάδιο. Οι συναρτήσεις που υλοποιείς εδώ είναι μία για κάθε σταθμό.

Note

Οι υπολογισμοί σε αυτή την άσκηση θα πρέπει να γίνονται με εντολές SIMD, όχι με βαθμωτές πράξεις.

1. Συνδύασε τις αποδόσεις από δύο γραμμές

Στο δάπεδο παραγωγής λειτουργούν δύο παράλληλες γραμμές συναρμολόγησης. Στο τέλος κάθε βάρδιας, και οι δύο γραμμές αναφέρουν τις αποδόσεις τους για 4 κάδους εξαρτημάτων ως αριθμούς απλής ακρίβειας. Ο πίνακας ελέγχου του επόπτη δείχνει τη συνδυασμένη απόδοση ανά κάδο, οπότε οι δύο αναφορές αθροίζονται κάδο προς κάδο. Και τα τρία buffer διαχειρίζεται το σύστημα αναφορών και είναι πάντα ευθυγραμμισμένα στα 16 byte.

Όρισε μια συνάρτηση sum_yields που, δεδομένων των αποδόσεων ανά κάδο από τα line_a και line_b, γράφει το άθροισμα ανά κάδο στο result. Τα ορίσματα, με τη σειρά, είναι:

  1. line_a: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 4 αριθμούς κινητής υποδιαστολής 32 bit
  2. line_b: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 4 αριθμούς κινητής υποδιαστολής 32 bit
  3. result: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte όπου η συνάρτηση θα γράψει 4 αριθμούς κινητής υποδιαστολής 32 bit

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής, αντίθετα το αποτέλεσμα αποθηκεύεται στο buffer που περνάς (result):

line_a = {10.0, 20.0, 30.0, 40.0};
line_b = { 5.0, 15.0, 25.0, 35.0};
sum_yields(line_a, line_b, result);
// => result == {15.0, 35.0, 55.0, 75.0};

2. Υπολόγισε την κλιμακωμένη απόκλιση

Μεταξύ των εργασιών, ένας σταθμός δειγματοληψίας μετρά κατεργασμένα εξαρτήματα και τα συγκρίνει με τις διαστάσεις-στόχους. Οι μετρημένες τιμές καταφθάνουν από ένα όργανο ακριβείας σε ένα buffer που είναι πάντα ευθυγραμμισμένο στα 16 byte. Οι τιμές-στόχοι προέρχονται από το πακέτο εντολής εργασίας, το οποίο δεν έχει εγγύηση ευθυγράμμισης. Η απόκλιση πολλαπλασιάζεται με έναν συντελεστή ευαισθησίας ανά άξονα πριν περάσει στον επόμενο έλεγχο ανοχών. Για αυτό το στάδιο, ο επόμενος έλεγχος διαβάζει την απόκλιση απευθείας από έναν καταχωρητή SIMD και όχι από τη μνήμη.

Όρισε μια συνάρτηση scaled_deviation που, δεδομένων των measured και target και μιας sensitivity, γράφει την κλιμακωμένη απόκλιση στο result. Τα ορίσματα, με τη σειρά, είναι:

  1. measured: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit
  2. target: διεύθυνση μνήμης που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit (χωρίς εγγύηση ευθυγράμμισης)
  3. sensitivity: διεύθυνση μνήμης που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit (χωρίς εγγύηση ευθυγράμμισης)
  4. result: διεύθυνση μνήμης για ένα buffer όπου η συνάρτηση θα γράψει 2 αριθμούς κινητής υποδιαστολής 64 bit (χωρίς εγγύηση ευθυγράμμισης)

Η κλιμακωμένη απόκλιση είναι (measured - target) * sensitivity, ανά λωρίδα.

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής, αντίθετα το αποτέλεσμα αποθηκεύεται στο buffer που περνάς (result) ως ζεύγος αριθμών κινητής υποδιαστολής 64 bit.

measured    = {10.5, 20.5};
target      = {10.0, 20.0};
sensitivity = { 2.0,  4.0};
scaled_deviation(measured, target, sensitivity, result);
// => result == {1.0, 2.0};

3. Βαθμονόμησε μια παρτίδα

Πριν τα εξαρτήματα μπουν στο τελικό τμήμα επιθεώρησης, οι μετρήσεις διαστάσεων από μια παρτίδα 4 εξαρτημάτων μετατρέπονται ξανά σε διπλή ακρίβεια και διορθώνονται για τη γνωστή θερμική μετατόπιση.

Οι 4 ακατέργαστες μετρήσεις φτάνουν ως αριθμοί απλής ακρίβειας. Για κάθε σειρά 2 μετρήσεων, η βαθμονόμηση υπολογίζει reference / (raw - offset) και στη συνέχεια πολλαπλασιάζει αυτό το αποτέλεσμα με έναν σταθερό συντελεστή μετατροπής 0.5.

Σημείωσε ότι το reference και το offset είναι τα ίδια και για τις δύο σειρές της παρτίδας. Ο συντελεστής μετατροπής είναι ο ίδιος σε κάθε βαθμονόμηση, οπότε αντί να τον λαμβάνεις ως όρισμα, θα πρέπει να τον δηλώσεις ως συσκευασμένη σταθερά 16 byte στη μνήμη.

Όρισε μια συνάρτηση calibrate_batch που, δεδομένων μιας παρτίδας raw, ενός reference, ενός offset και ενός buffer result, γράφει τις βαθμονομημένες τιμές στο result. Τα ορίσματα, με τη σειρά, είναι:

  1. raw: διεύθυνση μνήμης που περιέχει 4 αριθμούς κινητής υποδιαστολής 32 bit (χωρίς εγγύηση ευθυγράμμισης)
  2. reference: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit
  3. offset: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit
  4. result: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte όπου η συνάρτηση θα γράψει 4 αριθμούς κινητής υποδιαστολής 64 bit (32 byte συνολικά)

Η συνάρτηση θα πρέπει, για κάθε μία από τις δύο σειρές της παρτίδας:

  1. Να μετατρέπει τις 2 ακατέργαστες τιμές απλής ακρίβειας της σειράς σε διπλή ακρίβεια.
  2. Να αφαιρεί το offset από το ζεύγος που προέκυψε.
  3. Να διαιρεί το reference με το αποτέλεσμα.
  4. Να πολλαπλασιάζει ανά λωρίδα κάθε αριθμό κινητής υποδιαστολής 64 bit με τον σταθερό συντελεστή μετατροπής 0.5.
  5. Να αποθηκεύει τους 2 αριθμούς κινητής υποδιαστολής 64 bit στα αντίστοιχα 16 byte του result.

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής, αντίθετα το αποτέλεσμα αποθηκεύεται στο buffer που περνάς (result):

raw       = {10.0, 20.0, 25.0, 50.0};
reference = {100.0, 100.0};
offset    = {  5.0,  10.0};
calibrate_batch(raw, reference, offset, result);
// => result == {10.0, 5.0, 2.5, 1.25};

4. Κανονικοποίησε τις βαθμολογίες κατά μήκος μιας γραμμής

Στο τέλος της γραμμής, κάθε εξάρτημα έχει μια βαθμολογία ποιότητας που πρέπει να κανονικοποιηθεί πριν αναφερθεί. Κάθε βαθμολογία πολλαπλασιάζεται με το δικό της κέρδος ανά εξάρτημα και στη συνέχεια διαιρείται με μια κλίμακα παραγωγής ανά άξονα. Το buffer των κερδών περιέχει ένα κέρδος ανά εξάρτημα.

Η κλίμακα παραγωγής είναι η ίδια για όλη τη διαδικασία και περνά σε μια διεύθυνση μνήμης που περιέχει δύο αριθμούς κινητής υποδιαστολής 64 bit. Οι βαθμολογίες ενημερώνονται επί τόπου: το ίδιο buffer που περιείχε τις αρχικές βαθμολογίες περιέχει τώρα τις κανονικοποιημένες βαθμολογίες.

Όρισε μια συνάρτηση normalize_scores που, δεδομένων ενός πίνακα scores, ενός πίνακα gains, μιας scale και ενός πλήθους n, κανονικοποιεί τις βαθμολογίες επί τόπου. Τα ορίσματα, με τη σειρά, είναι:

  1. scores: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει n αριθμούς κινητής υποδιαστολής 64 bit (τροποποιείται επί τόπου)
  2. gains: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει n αριθμούς κινητής υποδιαστολής 64 bit
  3. scale: διεύθυνση μνήμης ευθυγραμμισμένη στα 16 byte που περιέχει 2 αριθμούς κινητής υποδιαστολής 64 bit
  4. n: ένας θετικός άρτιος ακέραιος 64 bit (ελάχιστο 2), το πλήθος των βαθμολογιών που θα κανονικοποιηθούν

Οι δύο λωρίδες της κλίμακας εφαρμόζονται στα δύο στοιχεία κάθε ζεύγους. Η πρώτη λωρίδα κλιμακώνει το πρώτο στοιχείο κάθε ζεύγους (λωρίδες σε άρτιες θέσεις), η δεύτερη λωρίδα κλιμακώνει το δεύτερο (λωρίδες σε περιττές θέσεις). Κάθε βαθμολογία πολλαπλασιάζεται με το αντίστοιχο κέρδος της και στη συνέχεια διαιρείται με την κατάλληλη λωρίδα της κλίμακας: score * gain / scale_lane.

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής, αντίθετα το αποτέλεσμα αποθηκεύεται στο buffer εισόδου (scores), στην ίδια θέση με την αντίστοιχη score που κανονικοποιήθηκε:

n      = 4
scores = {10.0, 20.0, 30.0, 40.0};
gains  = { 2.0,  1.5,  1.0,  0.5};
scale  = { 2.0,  4.0};
normalize_scores(scores, gains, scale, n);
// => scores == {10.0, 7.5, 15.0, 5.0};
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
x86-64 Assembly Exercism

Έτοιμος να ξεκινήσεις την άσκηση Γραμμή παραγωγής;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.