| 1 | = Μεταγλώττιση και Εκτέλεση προγραμμάτων με χρήση HTM (Intel TSX) = |
| 2 | Στα cluster μας υπάρχουν: |
| 3 | |
| 4 | - 2 μηχανήματα με Haswell επεξεργαστές (haci3 και broady) που υποστηρίζουν το HTM της Intel. |
| 5 | - ~~2 μηχανήματα με Power8 επεξεργαστές (power{1,2}) που υποστηρίζουν το HTM της IBM.~~ |
| 6 | ==== Intel TSX ==== |
| 7 | Το HTM της Intel υποστηρίζει δύο διαφορετικούς τρόπους εκτέλεσης. Το HLE (Hardware Lock Elision) και το RTM (Restricted Transactional Memory). |
| 8 | Το HLE επιτρέπει την εκτέλεση του critical section μία φορά με speculation (το lock γίνεται μόνο read όχι acquire) και αν αποτύχει εκτελείται τη δεύτερη φορά αφού πρώτα γίνει acquire το lock. Ένα πρόγραμμα το οποίο χρησιμοποιεί το HLE είναι συμβατό και μπορεί να εκτελεστεί και σε επεξεργαστές που δεν υποστηρίζουν TSX, σε αυτή την περίπτωση το critical section εκτελείται κατευθείαν σε lock mode. Το RTM δεν προσφέρει αυτή την συμβατότητα με προηγούμενες γενιές επεξεργαστών αλλά προσφέρει μεγαλύτερη ευελιξία ως προς τις ενέργειες που μπορούν να γίνουν μετά από κάποιο transactional abort. Ο προγραμματιστής ορίζει μία διεύθυνση μνήμης στην οποία βρίσκεται ο κώδικας ο οποίος θα εκτελεστεί σε περίπτωση abort (fallback handler). |
| 9 | |
| 10 | [https://software.intel.com/en-us/blogs/2013/05/20/using-hle-and-rtm-with-older-compilers-with-tsx-tools Εδώ] υπάρχουν οδηγίες για τη συγγραφή προγραμμάτων που χρησιμοποιούν το TSX. |
| 11 | |
| 12 | Τα παρακάτω intrinsics του gcc μπορούν να χρησιμοποιηθούν*: |
| 13 | |
| 14 | - `int _xbegin(void)` : Δηλώνει την αρχή ενός transaction. Ως διεύθυνση του fallback handler ορίζεται η διεύθυνση ακριβώς κάτω από την εντολή `_xbegin()`. Η τιμή επιστροφής χρησιμοποιείται για να ξέρουμε αν είμαστε μέσα σε transaction ή έχει γίνει κάποιο abort. Η τιμή επιστροφής είναι το περιεχόμενο του EAX (που περιέχει το status του transaction). Μπορούμε να ελέγξουμε αν το transaction έχει ξεκινήσει ή αν έχει γίνει abort κάνοντας ένα λογικό and (τελεστής `&` στη C) ανάμεσα στην τιμή επιστροφής και σε κάθε μία από τις παρακάτω σταθερές: |
| 15 | - `_XBEGIN_STARTED` το transaction ξεκίνησε επιτυχώς. |
| 16 | - `_XABORT_{EXPLICIT,RETRY,CONFLICT,CAPACITY,DEBUG,NESTED}` το transaction έγινε abort για τον αντίστοιχο λόγο (το RETRY δίνει μία εκτίμηση για το αν το transaction έχει πιθανότητες να κάνει commit αν ξαναπροσπαθήσουμε σε speculation mode, το έχω δεί να είναι 1 μόνο όταν είναι και το CONFLICT 1). |
| 17 | - `void _xend(void)` : Δηλώνει το τέλος ενός transaction. Γίνεται commit όλων των αλλαγών που κάναμε στην μνήμη. |
| 18 | - `void _xabort(const unsigned int status)` : Explicit abort ενός transaction. Το status χρησιμοποιείται για να δείχνουμε τον λόγο για τον οποίο έγινε το abort. |
| 19 | - `int _xtest()` : Επιστρέφει 1 αν τη στιγμή της κλήσης της εκτελείται κάποιο transaction, 0 διαφορετικά. |
| 20 | |
| 21 | * Οι εκδόσεις του gcc 4.8 και πάνω υποστηρίζουν αυτά τα intrinsics, ωστόσο για να έχουμε συμβατότητα και με παλιότερες εκδόσεις καλό είναι να χρησιμοποιούμε τα header files (rtm.h etc) που δίνονται στο [1]. Για τη μεταγλώτιση δεν απαιτούνται ειδικά flags στον gcc. |
| 22 | |
| 23 | === Παράδειγμα === |
| 24 | {{{ |
| 25 | #include "rtm.h" |
| 26 | |
| 27 | int num_retries = 10; |
| 28 | int aborts = 0; |
| 29 | int status; |
| 30 | some_kind_of_lock lock; //> e.g. pthread_spinlock_t lock; |
| 31 | |
| 32 | while (1) { |
| 33 | int status = _xbegin(); |
| 34 | if (status == _XBEGIN_STARTED) { |
| 35 | //> Transaction started successfully |
| 36 | |
| 37 | //> Check if lock is free, else abort explicitly. |
| 38 | //> This also adds the lock to the read set so we abort if another thread acquires(writes to) the lock. |
| 39 | if (!lock_is_free(lock)) |
| 40 | _xabort(0xff); |
| 41 | |
| 42 | } else { |
| 43 | //> Transaction was aborted |
| 44 | aborts++; |
| 45 | |
| 46 | //> Abort reason was... |
| 47 | if (status & _XBEGIN_CONFLICT) |
| 48 | //> data conflict |
| 49 | else if (status & _XBEGIN_CAPACITY) |
| 50 | //> capacity (transaction read or write set overflow) |
| 51 | ... //> Check for other abort reasons the same way. |
| 52 | |
| 53 | //> If we exceeded the number of retries acquire the lock and move forward to execute the |
| 54 | //> critical section. Else retry in speculative mode (e.g. go back to the while loop). |
| 55 | if (aborts >= num_retries) |
| 56 | acquire_lock(lock); |
| 57 | else |
| 58 | continue; |
| 59 | } |
| 60 | |
| 61 | //> Critical section goes here... |
| 62 | |
| 63 | if (_xtest()) |
| 64 | _xend(); |
| 65 | else |
| 66 | release_lock(lock); |
| 67 | break; |
| 68 | } |
| 69 | }}} |