Εισαγωγή στη μηχανική των switch statements
Οι εντολές switch φαίνονται συχνά απλές στη σύνταξη, αλλά η εσωτερική τους υλοποίηση από έναν compiler όπως ο GCC είναι ιδιαίτερα σύνθετη. Ο compiler καλείται να αποφασίσει αν μια δομή διακλάδωσης θα μεταφραστεί σε δέντρο αποφάσεων (decision tree), σε πίνακα αλμάτων (jump tables) ή σε συστοιχίες bit (bitsets), ανάλογα με την κατανομή και την πυκνότητα των τιμών (case labels).
Στρατηγικές μετατροπής και heuristics
Όταν εξετάζουμε την έξοδο ενός compiler σε επίπεδο assembly, παρατηρούμε ότι ο GCC εφαρμόζει αυστηρά όρια (thresholds):
- Jump Tables: Για να συμφέρει η δημιουργία ενός jump table, η αναλογία μεταξύ των εγγραφών του πίνακα και των συγκρίσεων (comparisons) πρέπει να είναι κάτω από ένα συγκεκριμένο όριο (συνήθως 8 προς 1 για βελτιστοποίηση ταχύτητας και 2 προς 1 για μέγεθος με τη σημαία
-Os). - Bitsets: Όταν οι τιμές είναι αραιές αλλά σχηματίζουν συγκεκριμένα μοτίβα, ο GCC εκμεταλλεύεται bitsets, μετατρέποντας τις συγκρίσεις σε bitwise ελέγχους μέσω εντολών όπως η
bt(bit test) στους x86 επεξεργαστές.
Η αρχιτεκτονική του pass_lower_switch στον GCC
Στον πηγαίο κώδικα του GCC, το πέρασμα pass_lower_switch είναι υπεύθυνο για την ανάλυση της δομής. Η εσωτερική λειτουργία βασίζεται σε αλγορίθμους δυναμικού προγραμματισμού (dynamic programming):
- Αρχικά, οι ετικέτες (
case labels) ομαδοποιούνται σε δομές γνωστές ωςcluster. - Στη συνέχεια, ο αλγόριθμος αναζητά την ελάχιστη δυνατή σύνθεση από bitsets και jump tables που καλύπτουν τα δεδομένα με το μικρότερο δυνατό κόστος σε CPU cycles.
- Όσα τμήματα δεν πληρούν τις προϋποθέσεις κερδοφορίας, καταλήγουν τελικά στα φύλλα ενός παραδοσιακού δέντρου αποφάσεων (decision tree).
Αυτή η πολυεπίπεδη προσέγγιση εξηγεί γιατί ακόμη και μικρές διαφορές στις τιμές των case μπορούν να αλλάξουν δραστικά την παραγόμενη assembly, επηρεάζοντας το branch prediction και το συνολικό throughput της εφαρμογής.