Η nVIDIA λύνει τα προβλήματα των πολυπύρηνων?
Όποιος έχει κάνει έστω και ένα μάθημα στον προγραμματισμό στους υπολογιστές, γνωρίζει πόσο δύσκολο είναι να κάνεις έναν υπολογιστή να κάνει ακριβώς αυτό που θες. Αυτό είναι, ειδικά για τους νέους developers, ένας μεγάλος πονοκέφαλος, όπως επίσης και για τους "παλιούς", που αντιμετωπίζουν τα προβλήματα της τεχνολογίας που αναπτύσσεται. Μιλώντας γενικά, μετά από ένα σημείο δεν τίθεται θέμα προγραμματισμού από τον άνθρωπο, μιας και έχει χρησιμοποιήσει όλες τις δυνατότητες που του δίνονται, αλλά είναι θέμα των εργαλείων που αυτός χρησιμοποιεί. Η γλώσσα που χρησιμοποιεί, το λειτουργικό σύστημα, πολλά πράγματα και παράγοντες παίζουν σημαντικό ρόλο. Με λίγα λόγια, όπως λένε και οι ίδιοι: "Το να πεθαίνεις είναι εύκολο - το δύσκολο είναι να προγραμματίζεις".
Όποιος έχει κάνει έστω και ένα μάθημα στον προγραμματισμό στους υπολογιστές, γνωρίζει πόσο δύσκολο είναι να κάνεις έναν υπολογιστή να κάνει ακριβώς αυτό που θες. Αυτό είναι, ειδικά για τους νέους developers, ένας μεγάλος πονοκέφαλος, όπως επίσης και για τους "παλιούς", που αντιμετωπίζουν τα προβλήματα της τεχνολογίας που αναπτύσσεται. Μιλώντας γενικά, μετά από ένα σημείο δεν τίθεται θέμα προγραμματισμού από τον άνθρωπο, μιας και έχει χρησιμοποιήσει όλες τις δυνατότητες που του δίνονται, αλλά είναι θέμα των εργαλείων που αυτός χρησιμοποιεί. Η γλώσσα που χρησιμοποιεί, το λειτουργικό σύστημα, πολλά πράγματα και παράγοντες παίζουν σημαντικό ρόλο. Με λίγα λόγια, όπως λένε και οι ίδιοι: "Το να πεθαίνεις είναι εύκολο - το δύσκολο είναι να προγραμματίζεις".
Τώρα, φανταστείτε αυτή την επίπονη διαδικασία ένας προγραμματιστής να αναγκαστεί να την κάνει όχι μόνο για έναν πυρήνα, αλλά για πολλούς. Τώρα εισερχόμαστε στον κόσμο του παράλληλου προγραματισμού, όπου αρχίζει το "γλέντι". Η ιδέα του να γράφουμε ξεχωριστά προγράμματα που μπορούν να τρέχουν σε διαφορετικούς πυρήνες λέγεται multi-threading. Αυτό βασικά σημαίνει ότι περισσότερα από ένα κομμάτια του προγράμματος τρέχουν ταυτόχρονα, αλλά σε διαφορετικούς πυρήνες. Ενώ μοιάζει εύκολο όμως, δεν είναι. Υποθέστε ότι προγραμματίζετε μια μηχανή γραφικών για έναν παιχνίδι και πρέπει να υπάρχει συντονισμός ανάμεσα στην τοποθεσία των χαρακτήρων στον τρισδιάστατο κόσμο, μαζί με τις κινήσεις τους, μαζί με τον ήχο. Όλα αυτά, πρέπει να συγχρονιστούν.
Ας υποθέσουμε ότι η δουλειά με τον ήχο είναι πολύ εύκολη να γίνει (που, σε γενικές γραμμές, είναι). Ο υπολογιστής μόνος του μπορεί να είναι ικανός να παράγει ήχο για ένα video game στα 7.000 FPS. Ένας developer μπορεί να αποφασίσει να δώσει αυτή τη δουλειά σε μια εφαρμογή, που θα αναλάβει ένας πυρήνας. Η κίνηση του παίχτη, ας υποθέσουμε ότι μπορεί να γίνει render στα 400 FPS, ενώ η αναπαράσταση του τρισδιάστατου κόσμου στα 60 FPS. Αν κάθε μια από αυτές τις δουλειές έχει και τη δική της εφαρμογή, στο δικό της πυρήνα, κάποιος πρέπει να τις συγχρονίσει, αλλιώς το παιχνίδι δε θα φαίνεται σωστά.
Μια άλλη παρενέργεια των πολυπύρηνων που τρέχουν πολλαπλές εφαρμογές την ίδια στιγμή λέγεται "stale data". Ο κάθε επεξεργαστής έχει τη δική του cache, και κάθε επεξεργαστής προσπαθεί πάντα να εκμεταλλεύεται τις δυνατότητές του στο 100%. Αυτό σημαίνει ότι τα δεδομένα κυρίως φυλάσσονται και διαμοιράζονται από την L1 και την L2 cache, που είναι συνδεδεμένες με τον κάθε πυρήνα. Μια shared L3 cache, αφαιρεί τα λιγότερο σημαντικά δεδομένα, αλλά τα δεδομένα στην L1 και L2 cache, αλλάζουν για τον ένα πυρήνα, χωρίς οι άλλη πυρήνες να γνωρίζουν για την αλλαγή αυτή. Το caching πρωτόκολλο που δημιουργήθηκε (MOESI - Modified, Owner, Exclusive, Shared, Invalid), προσπαθεί να βάλει τα πράγματα σε τάξη, αλλά αυτό αναγκαστικά σημαίνει μεγάλη κίνηση στο bus, ενώ υπάρχει καθυστέρηση στην επικοινωνία με τους άλλους πυρήνες για αυτές τις αλλαγές στην cache.
Αν ο πυρήνας 1 και ο πυρήνας 2, ταυτόχρονα υπολογίζουν δεδομένα βασισμένα σε ένα συγκεκριμένο αντικείμενο, υπάρχει πιθανότητα να αλλάξουν τα δεδομένα αυτά και αν γίνει κάτι τέτοιο, τότε τα αποτελέσματα κάποιων από τους υπολογισμούς θα είναι λανθασμένα.
Που κολλάει αυτό? Μα φυσικά στο παραπάνω παράδειγμα για τα παιχνίδια - αν ο πυρήνας 1 υπολόγιζε την κίνηση και ο πυρήνας 2 το τρισδιάστατο περιβάλλον, η παραμικρή αλλαγή στα δεδομένα θα άλλαζε τον συγχρονισμό. Έτσι, θα βλέπαμε λάθος rendering στην οθόνη μας, με αποτέλεσμα, αν αυτό συμβαίνει συχνά, το παιχνίδι να είναι "jerky" και στην ουσία, δε θα μπορούμε να παίξουμε.
Για να λυθούν αυτά τα προβλήματα, οι developers δημιούργησαν πράγματα όπως οι semaphores, που επιτρέπουν σε πολλαπλούς πυρήνες να έχουν πρόσβαση σε δεδομένα μόνο όταν "έρχεται η σειρά τους". Με αυτόν τον τρόπο, κάθε πυρήνας θα μπορεί να έχει πρόσβαση μόνο στα πιο πρόσφατα δεδομένα, στα οποία όλοι οι υπόλοιποι πυρήνες βασίζονται.
Αλλά και πάλι το πρόβλημα του συγχρονισμού υπάρχει. Όπως προείπαμε, κάποιες εφαρμογές τρέχουν ταχύτερα από τις άλλες. Έτσι στον "αγώνα δρόμου" του πυρήνα 1 με τον πυρήνα 2, μπορεί να έχουμε έλλειψη συγχρονισμού λόγω του ότι ο πυρήνας 1 τελείωσε ταχύτερα την εργασία του από τον 2.
Όλα αυτά τα προβλήματα, μαζί με αμέτρητα άλλα ακόμα, όσο ανεβαίνει ο αριθμός των πυρήνων, τόσο οξύνονται. Και οι εφαρμογές όπως η μηχανή CUDA της nVIDIA (και η αντίστοιχη, αν και πολύ υποδεέστερη CTM Engine της ATi), βοηθάνε στο να μη χρείαζεται μια τόσο "σκληρή" δουλειά να γίνει από έναν προγραμματιστή. Αλλά, την ίδια ώρα, βαραίνουν τη μηχανή γραφικών, προσπαθώντας να αφαιρέσουν τις ατέλειες της παράλληλης επεξεργασίας. Το πρόβλημα αυτό βέβαια, μπορεί να λυθεί αν κάποιος φτιάξει μόνος του τον προγραμματισμό της μηχανής - με το εργαλείο αυτό όμως γλιτώνει και χρόνο και κυρίως, αρκετό κόπο.
Το σύστημα CUDA της nVIDIA, αρχικά ανεπτυγμένο για τους πυρήνες των καρτών γραφικών, βρίσκει εφαρμογή πλέον σε ένα πολύ μεγάλο αριθμό εφαρμογών στους υπολογιστές. Σαν αποτέλεσμα, μάλλον δε θα είναι κάποιος σχεδιαστής επεξεργαστών αυτός ο οποίος θα λύσει το πρόβλημα και τις προκλήσεις της πολυπύρηνης, παράλληλης επεξεργασίας, στον προγραμματισμό, αλλά μια εταιρεία κατασκευής καρτών γραφικών.
Αξιοπερίεργο μεν, σίγουρα ευπρόσδεκτο δε.
Τώρα, φανταστείτε αυτή την επίπονη διαδικασία ένας προγραμματιστής να αναγκαστεί να την κάνει όχι μόνο για έναν πυρήνα, αλλά για πολλούς. Τώρα εισερχόμαστε στον κόσμο του παράλληλου προγραματισμού, όπου αρχίζει το "γλέντι". Η ιδέα του να γράφουμε ξεχωριστά προγράμματα που μπορούν να τρέχουν σε διαφορετικούς πυρήνες λέγεται multi-threading. Αυτό βασικά σημαίνει ότι περισσότερα από ένα κομμάτια του προγράμματος τρέχουν ταυτόχρονα, αλλά σε διαφορετικούς πυρήνες. Ενώ μοιάζει εύκολο όμως, δεν είναι. Υποθέστε ότι προγραμματίζετε μια μηχανή γραφικών για έναν παιχνίδι και πρέπει να υπάρχει συντονισμός ανάμεσα στην τοποθεσία των χαρακτήρων στον τρισδιάστατο κόσμο, μαζί με τις κινήσεις τους, μαζί με τον ήχο. Όλα αυτά, πρέπει να συγχρονιστούν.
Ας υποθέσουμε ότι η δουλειά με τον ήχο είναι πολύ εύκολη να γίνει (που, σε γενικές γραμμές, είναι). Ο υπολογιστής μόνος του μπορεί να είναι ικανός να παράγει ήχο για ένα video game στα 7.000 FPS. Ένας developer μπορεί να αποφασίσει να δώσει αυτή τη δουλειά σε μια εφαρμογή, που θα αναλάβει ένας πυρήνας. Η κίνηση του παίχτη, ας υποθέσουμε ότι μπορεί να γίνει render στα 400 FPS, ενώ η αναπαράσταση του τρισδιάστατου κόσμου στα 60 FPS. Αν κάθε μια από αυτές τις δουλειές έχει και τη δική της εφαρμογή, στο δικό της πυρήνα, κάποιος πρέπει να τις συγχρονίσει, αλλιώς το παιχνίδι δε θα φαίνεται σωστά.
Μια άλλη παρενέργεια των πολυπύρηνων που τρέχουν πολλαπλές εφαρμογές την ίδια στιγμή λέγεται "stale data". Ο κάθε επεξεργαστής έχει τη δική του cache, και κάθε επεξεργαστής προσπαθεί πάντα να εκμεταλλεύεται τις δυνατότητές του στο 100%. Αυτό σημαίνει ότι τα δεδομένα κυρίως φυλάσσονται και διαμοιράζονται από την L1 και την L2 cache, που είναι συνδεδεμένες με τον κάθε πυρήνα. Μια shared L3 cache, αφαιρεί τα λιγότερο σημαντικά δεδομένα, αλλά τα δεδομένα στην L1 και L2 cache, αλλάζουν για τον ένα πυρήνα, χωρίς οι άλλη πυρήνες να γνωρίζουν για την αλλαγή αυτή. Το caching πρωτόκολλο που δημιουργήθηκε (MOESI - Modified, Owner, Exclusive, Shared, Invalid), προσπαθεί να βάλει τα πράγματα σε τάξη, αλλά αυτό αναγκαστικά σημαίνει μεγάλη κίνηση στο bus, ενώ υπάρχει καθυστέρηση στην επικοινωνία με τους άλλους πυρήνες για αυτές τις αλλαγές στην cache.
Αν ο πυρήνας 1 και ο πυρήνας 2, ταυτόχρονα υπολογίζουν δεδομένα βασισμένα σε ένα συγκεκριμένο αντικείμενο, υπάρχει πιθανότητα να αλλάξουν τα δεδομένα αυτά και αν γίνει κάτι τέτοιο, τότε τα αποτελέσματα κάποιων από τους υπολογισμούς θα είναι λανθασμένα.
Που κολλάει αυτό? Μα φυσικά στο παραπάνω παράδειγμα για τα παιχνίδια - αν ο πυρήνας 1 υπολόγιζε την κίνηση και ο πυρήνας 2 το τρισδιάστατο περιβάλλον, η παραμικρή αλλαγή στα δεδομένα θα άλλαζε τον συγχρονισμό. Έτσι, θα βλέπαμε λάθος rendering στην οθόνη μας, με αποτέλεσμα, αν αυτό συμβαίνει συχνά, το παιχνίδι να είναι "jerky" και στην ουσία, δε θα μπορούμε να παίξουμε.
Για να λυθούν αυτά τα προβλήματα, οι developers δημιούργησαν πράγματα όπως οι semaphores, που επιτρέπουν σε πολλαπλούς πυρήνες να έχουν πρόσβαση σε δεδομένα μόνο όταν "έρχεται η σειρά τους". Με αυτόν τον τρόπο, κάθε πυρήνας θα μπορεί να έχει πρόσβαση μόνο στα πιο πρόσφατα δεδομένα, στα οποία όλοι οι υπόλοιποι πυρήνες βασίζονται.
Αλλά και πάλι το πρόβλημα του συγχρονισμού υπάρχει. Όπως προείπαμε, κάποιες εφαρμογές τρέχουν ταχύτερα από τις άλλες. Έτσι στον "αγώνα δρόμου" του πυρήνα 1 με τον πυρήνα 2, μπορεί να έχουμε έλλειψη συγχρονισμού λόγω του ότι ο πυρήνας 1 τελείωσε ταχύτερα την εργασία του από τον 2.
Όλα αυτά τα προβλήματα, μαζί με αμέτρητα άλλα ακόμα, όσο ανεβαίνει ο αριθμός των πυρήνων, τόσο οξύνονται. Και οι εφαρμογές όπως η μηχανή CUDA της nVIDIA (και η αντίστοιχη, αν και πολύ υποδεέστερη CTM Engine της ATi), βοηθάνε στο να μη χρείαζεται μια τόσο "σκληρή" δουλειά να γίνει από έναν προγραμματιστή. Αλλά, την ίδια ώρα, βαραίνουν τη μηχανή γραφικών, προσπαθώντας να αφαιρέσουν τις ατέλειες της παράλληλης επεξεργασίας. Το πρόβλημα αυτό βέβαια, μπορεί να λυθεί αν κάποιος φτιάξει μόνος του τον προγραμματισμό της μηχανής - με το εργαλείο αυτό όμως γλιτώνει και χρόνο και κυρίως, αρκετό κόπο.
Το σύστημα CUDA της nVIDIA, αρχικά ανεπτυγμένο για τους πυρήνες των καρτών γραφικών, βρίσκει εφαρμογή πλέον σε ένα πολύ μεγάλο αριθμό εφαρμογών στους υπολογιστές. Σαν αποτέλεσμα, μάλλον δε θα είναι κάποιος σχεδιαστής επεξεργαστών αυτός ο οποίος θα λύσει το πρόβλημα και τις προκλήσεις της πολυπύρηνης, παράλληλης επεξεργασίας, στον προγραμματισμό, αλλά μια εταιρεία κατασκευής καρτών γραφικών.
Αξιοπερίεργο μεν, σίγουρα ευπρόσδεκτο δε.