Table of Contents
Introduzione: L'influenza duratura del CISC sui moderni concorrenti
Tra i paradigmi architettonici più impetuosi è il Complesso di Istruzione Set Computing (CISC), una filosofia di progettazione che ha plasmato lo sviluppo del compilatore per decenni. A differenza della sua controparte Ridotto Istruzione Set Computing (RISC), che si basa su un piccolo insieme di istruzioni rapide e semplici, processori CISC imballano operazioni ricche, multi-step, come copia di stringa, valutazione polinomiale, o memoria-
Questo articolo esplora il profondo impatto del progetto CISC sulle strategie di ottimizzazione dei compilatori. Divulgheremo aree chiave tra cui selezione delle istruzioni, densità di codice, fusione di macro-operazione, allocazione dei registri sotto lunghezze di istruzione variabili, e le sfide moderne poste dalla micro-op decomposizione di CISC. Attraverso esempi concreti e riferimenti alle architetture del mondo reale, mostreremo come i compilatori si sono evoluti per sfruttare la potenza CISC, mitigando la sua complessità intrinseca.
Una breve storia di CISC: dai mainframe alla x86
Le radici del CISC risalgono agli anni '60 e '70, quando la memoria era costosa e i processori erano lenti. Per ridurre il numero di istruzioni necessarie per un dato programma, gli architetti hanno imballato più funzionalità in ogni istruzione. IBM System/360, introdotto nel 1964, è un esempio fondamentale: il suo set di istruzioni comprendeva aritmetica sui valori in memoria, rami condizionali con più codici di condizione, e operazioni di alto livello come "Compare e Branch"
La famiglia CISC più duratura è l'architettura x86, che ha origine con l'Intel 8086 nel 1978. x86 istruzioni di istruzioni evolute attraverso estensioni come MMX, SSE e AVX, accumulando centinaia di istruzioni che variano in modo selvaggio in lunghezza (1 a 15 byte). Nonostante la rivoluzione RISC degli anni '80, che ha dimostrato che le istruzioni più semplici potrebbero produrre velocità di clock più elevate e pipelining più facile—CISC ha continuato a dominante nei mercati a due server.
Strategie di ottimizzazione Compiler Impatto da CISC
La ricchezza di un set di istruzioni CISC crea sia opportunità che sfide per i compilatori. Di seguito esaminiamo le aree chiave in cui il design CISC guida le decisioni di ottimizzazione.
Selezione istruzioni: Bilanciamento di potenza e costi
In un sistema RISC, la selezione delle istruzioni è relativamente semplice: il compilatore mappa le operazioni di alto livello ad un piccolo insieme di semplici istruzioni, basandosi sull'ottimista per fondere le sequenze dove utile. In CISC, il compilatore deve scegliere da un vasto menu di istruzioni, ciascuno con diversa lunghezza, latenza e l'uso delle risorse.
I compilatori moderni (GCC, LLVM) utilizzano modelli di calcolo basati sui costi e di calcolo per prendere queste decisioni. Il backend specifico del bersaglio (ad esempio, x86 in LLVM) contiene centinaia di modelli che selezionano la sequenza di istruzioni migliore per un determinato modello IR.
Densità del codice e utilizzo della cache
Uno dei vantaggi storici di CISC è la densità di codice. Poiché un unico insegnamento CISC può sostituire più istruzioni RISC, il binario risultante è spesso più piccolo. Ad esempio, un CISC [[] istruzioni che carica da un indirizzo di memoria utilizzando un offset a 32 bit richiede solo 5-7 byte, mentre l'equivalente sequenza RISC (indirizzo di carico in registro, quindi il carico dal registro) potrebbe richiedere 8–12 byte.
I compilatori sfruttano la densità di codice attraverso tecniche come:
- Instruction accorciamento:[ Quando possibile, il compilatore sceglie la codifica più piccola (ad esempio, usando invece di con un immediato a 32 bit se il valore si adatta a 8 bit).
- Stack vs. allocazione dei registri:[ Nel codice CISC profondamente nidificati, i compilatori a volte versano i registri allo stack utilizzando istruzioni push/pop compatte ([] /] in x86 sono solo 1 byte ciascuno) piuttosto che con movimenti di registro-memory che prendono 3–4 byte classico.
- Utilizzando modalità di indirizzamento complesse: La modalità di indirizzamento indicizzata ([]) permette una singola istruzione da caricare da un elemento di array. I compilatori valutano attentamente se la codifica più lunga dell'istruzione (fino a 7 byte) viene compensata eliminando un'istruzione di calcolo dell'indirizzo separata.
Tuttavia, l'aumento della densità di codice non migliora sempre le prestazioni. Le istruzioni più lunghe possono richiedere più tempo per decodificare (soprattutto nelle prime x86 pipeline), e la codifica a lunghezza variabile rende più difficile la predizione pre-decodifica e branch.
Macro-Operazione Fusione e Micro-Op Decomposizione
I processori CISC moderni (x86 da Pentium M in avanti) interrompono internamente le istruzioni complesse in semplici microoperazioni (μops) che mappano al condotto di esecuzione. Ad esempio, un x86 è decomposto in un μop di carico, un μop aritmetico, e un μop di negozio. Questa decomposizione permette al processore di mantenere il motore pieno e sfruttare tre operazioni di controllo fuori-di-SC.
I responsabili devono tenere conto di questa micro-architettura.
- Macro-fusion:[ Alcune istruzioni CISC combinano due operazioni logiche (ad esempio, confrontare e ramificazione). Su x86, alcuni abbinamenti come seguiti da sono fusi dal processore in un unico μop specifico. Il compilatore può incoraggiare la fusione mantenendo il confronto e la filiale adiacente e evitando le istruzioni che modificano i codici di condizione L.
- Caching micro-op: I core x86 recenti (Intel Haswell e successivi) includono una cache μop che memorizza μop decodificato per loop. Per sfruttare questo, i compilatori generano codice che si adatta alle dimensioni della linea di cache μop (spesso 4–6 μops).
Interessante, la decomposizione micro-op a volte rende le istruzioni più semplici come RISC più veloci dei loro equivalenti CISC. Ad esempio, una sequenza di e ] utilizzando i registri possono essere decodificati in meno μop totali di un singolo che consuma tre μop slot.
Registrazione Allocation e Variabili istruzioni
L'assegnazione del registro è complicata da CISC perché molte istruzioni possono accedere direttamente alla memoria, rendendo la pressione del registro meno critico, ma anche introducendo i trade-off. Quando un compilatore assegna un registro per una variabile frequentemente utilizzata, può evitare operazioni di memoria, ma le istruzioni registrate-to-register risultanti sono tipicamente più lunghe (a causa di byte modifica) rispetto alle versioni di accesso alla memoria.
I compilatori CISC devono pesare il vantaggio di mantenere un valore in un registro contro la possibilità di aumentare la dimensione del codice e decodificare la latenza. Spesso utilizzano euristiche basate sulla profondità del loop e sulla dimensione della funzione. Ad esempio, in un loop caldo, il compilatore favorirà i registri per evitare la latenza della memoria, anche se ciò significa utilizzare le codificazioni di istruzioni più lunghe.
Un'altra sfida è il numero limitato di registri generali-purpose in x86: solo 8 in modalità 32-bit (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP) e 16 in modalità 64-bit. Questo scarsità costringe i compilatori a registrare intelligente divisione circa l'assegnazione del registro.
Sfide poste dalla complessità CISC
Mentre CISC offre molte opportunità di ottimizzazione, introduce anche ostacoli significativi per gli scrittori di compilatore.
Istruzione Scheduling e Variable Latency
In architetture RISC, la maggior parte delle istruzioni hanno la latenza predittiva, uniforme (spesso 1 ciclo per semplici ops ALU). Le istruzioni CISC possono avere una frequenza molto variabile. Ad esempio, un semplice [[FLT: 21] può richiedere 1 ciclo, mentre un (la divisione interi) richiede 20–40 cicli.
Complessità dell'ottimizzazione del peephole
Il set di istruzioni ricco di CISC richiede ottimisti di peephole che possono riconoscere i modelli di alto livello. Ad esempio, una sequenza come può essere sostituita da un singolo se il compilatore verifica che le bandiere di condizione non sono utilizzate altrove. Questa trasformazione salva due istruzioni e riduce la pressione del registro. Tuttavia, il modello deve essere sicuro: la posizione della memoria potrebbe essere applicata da un altro thread o alia
LLVM e GCC moderni hanno ampi passaggi di peephole che funzionano durante il backend specifico del bersaglio. Ad esempio, il passaggio di LLVM [ sostituisce alcuni modelli di basso livello con istruzioni CISC più efficienti. Questo passaggio è euristico-driven e deve essere accuratamente mantenuto come nuove micro-architetture del processore introdurre diversi trade-off. Inoltre, i compilatori spesso abbassano IR a CISC istruzioni presto
Considerazioni di potenza e termica
Anche se non è sempre importante un problema di compilazione, il consumo di energia è sempre più importante. Le istruzioni CISC che collegano più unità di esecuzione (ad esempio, che si fonde moltiplicano-add) possono causare alti punti di potenza dinamici.
Opportunità: Imparare CISC per i guadagni di performance
Nonostante la complessità, il ricco set di istruzioni CISC offre opportunità di ottimizzazione uniche che RISC spesso non può corrispondere.
Istruzioni specializzate per i carichi di lavoro crittografici e multimediali
Le famiglie CISC come x86 hanno accumulato una vasta gamma di istruzioni specializzate.
- AES-NI:[] , [, e le relative istruzioni accelerano le operazioni standard di crittografia avanzata. I compilatori possono riconoscere i loop che eseguono i turni AES e sostituirli con queste singole istruzioni, ottenendo fattori di velocità 10-20x sulle implementazioni del software [Intel AES‐NI Guida di ottimizzazione].
- estensioni di SSHA:[ e altri velocizzano gli algoritmi di hashing.
- AVX-512:[] Il multiplo, lo spargimento/gather e il rilevamento dei conflitti possono accelerare drasticamente HPC e il codice vettoriale. I compilatori utilizzano i passaggi di auto-vettura per generare queste istruzioni, spesso con controlli runtime per il supporto della CPU.
- BMI/BMI2:[] Istruzioni di manipolazione bit (ad esempio [, []) consentono l'implementazione compatta di alcune operazioni bit-field.
Per sfruttare queste caratteristiche, i compilatori devono conoscere il set di funzionalità della CPU di destinazione. LLVM e GCC utilizzano i controlli CPUID e le annotazioni attributo specifiche del target (come ). In molti-parts, il compilatore può generare più percorsi di codice e selezionare quello appropriato durante il runtime attraverso la funzione multiversioning.
Codice legacy Compatibilità e Riscrittura Binary
Per le ottimizzazioni dei compilatori, significa che il codice degli oggetti esistente dai compilatori più vecchi può talvolta essere migliorato tramite strumenti di riscrittura binaria (ad esempio, strumento PIN di Intel o ottimizzatori automatici come BOLT). Questi strumenti eseguono le ottimizzazioni di ultima miglia che i compilatori non possono facilmente fare perché non hanno informazioni di runtime.
Conclusione: Il ruolo evolutivo del CISC nello sviluppo del Compiler
L’impatto del progetto CISC sulle strategie di ottimizzazione dei compilatori è profondo e multiforme: dalla selezione delle istruzioni e dalla densità del codice alla fusione e all’allocazione dei registri micro-op, la complessità del CISC costringe i compilatori ad utilizzare analisi e modelli di costo sofisticati.
In attesa di un futuro, CISC rimarrà probabilmente rilevante attraverso l'ecosistema x86, mentre ARM (un progetto RISC) guadagna terreno in server e computer portatili. Ciò significa che gli scrittori compilatori devono mantenere più obiettivi backend, ciascuno con la propria serie di trade-off. Per gli sviluppatori, capire come CISC forma l'uscita compilatore algoritmo è la chiave per scrivere codice che può essere ottimizzato in modo efficace, ad esempio, utilizzando funzioni intrinseche per istruzioni specializzate o scrivendo loopstruttive che sono state
Per ulteriori informazioni, consultare il Intel® 64 e IA-32 Architectures Software Developer Manuals], che dettagliano ogni istruzione x86 e il suo comportamento, e il Agner Fog’s ottimizzazione manuali] che forniscono tabelle di micro-architettura utilizzate dagli scrittori del compilatore.