Architettura del computer: von Neumann, CPU, memorie e ciclo macchina


Architettura del computer: von Neumann, CPU, memorie e ciclo macchina
1) «Il computer ha 500 GB di memoria»: la parola che sbaglia da sola
2) Il modello di von Neumann: che cosa c'è davvero nel rapporto del 1945
3) Von Neumann o Harvard: una memoria o due (e perché il tuo PC è tutti e due)
4) Il collo di bottiglia: che cosa disse davvero Backus (e che cosa non disse)
5) Dentro la CPU: unità di controllo, ALU, registri (e i nomi che cambiano fra i libri)
6) Il ciclo macchina passo per passo: la traccia di esecuzione, riga per riga
7) La gerarchia delle memorie: i numeri veri, misurati su processori con un nome
8) I bus e il conto 2ⁿ: quanta memoria posso indirizzare
9) Il clock: perché i gigahertz non misurano la velocità
10) Dodici esercizi svolti passo passo
11) Gli errori che ti costano il voto
12) Domande frequenti

«Il computer ha 500 GB di memoria»: la parola che sbaglia da sola



Partiamo da questa frase perché è sbagliata in un punto solo. Non nel numero: 500 GB è una capacità plausibile. È sbagliata la parola «memoria» — e non è colpa tua, è colpa di come l'italiano ha diviso il mondo.

L'inglese ha due sostantivi diversi: memory per la RAM e storage per il disco. L'italiano ne ha uno. Il vocabolario scolastico prova a rattoppare con due aggettivi, memoria centrale e memoria di massa, ma lascia lo stesso nome a due oggetti che non si somigliano in niente: quindi ti dà due etichette da imparare, non due idee da distinguere. I sistemi operativi, che il problema ce l'hanno addosso tutti i giorni, sono più netti del tuo libro: Android in italiano ha una voce «Memoria» e una voce «Spazio di archiviazione», Windows ha «Memoria» e «Archiviazione». Loro le hanno separate.

Poi c'è il negozio, e quello lavora attivamente contro di te. Il telefono te lo vendono come «8 GB di RAM + 256 GB di ROM». Ma su quei 256 GB tu ci scrivi le foto, quindi di sola lettura non sono per niente, mentre il giorno dopo il libro ti spiega che ROM sta per read only memory e contiene il firmware di avvio. Non stai capendo male: sono due usi diversi della stessa sigla, e uno dei due è marketing. Quando in verifica ti chiedono «la ROM è una memoria di massa?», la risposta giusta è no, e il criterio è questo: la ROM vera è piccola, non riscrivibile dall'utente e non ci si salvano file, mentre una memoria di massa è per definizione riscrivibile e ospita un file system. La ragione per cui hai esitato, invece, è appena stata scritta qui sopra.

I tre numeri che separano davvero RAM e disco

Non è una questione di parole. Sono tre grandezze misurabili, e su tutte e tre la distanza è enorme.

Capacità. Una macchina scolastica tipica ha 8 GB di RAM e 500 GB di disco. Attenzione però a come si fa il rapporto, perché è la prima trappola del capitolo: i due numeri non sono scritti nella stessa convenzione. La RAM si vende a potenze di due, quindi «8 GB» sono in realtà 8 GiB; il disco si vende in giga decimali. Si torna sempre ai byte grezzi:

   disco   500 GB  =  500 x 10^9 B          =  500.000.000.000 B
   RAM       8 GiB =  8 x 2^30 B            =    8.589.934.592 B
   rapporto  500.000.000.000 : 8.589.934.592  =  58,2
Circa 58 volte tanto, non 62,5. Il 62,5 esce dal dividere le etichette del negozio l'una per l'altra, cioè facendo esattamente l'errore che questa guida ti insegna a evitare e che la sezione 11 rimette in fila con la regola operativa: massa e rete in decimale, RAM e indirizzamento in binario. Cinquantotto volte resta comunque un abisso: se «la memoria» fosse una cosa sola, non si spiegherebbe perché aprire venti schede del browser rallenta tutto mentre il disco è pieno al 20 per cento.

Persistenza. Una cella di DRAM (dynamic random access memory, memoria dinamica ad accesso casuale) è 1 transistor più 1 condensatore — la sigla che trovi nei testi è 1T1C. Il condensatore perde carica da solo, quindi ogni riga va riletta e riscritta. JEDEC, l'ente che pubblica gli standard delle memorie, fissa la finestra di refresh a 64 ms per le DRAM fino alla DDR4 a temperatura normale: 1 ÷ 0,064 = 15,6 volte al secondo. Il valore non è universale, e vale la pena saperlo perché qui datiamo tutto: sopra gli 85 °C la finestra si dimezza a 32 ms, e la DDR5 adotta i 32 ms anche a temperatura normale. Quello che con lei si è accorciato è l'intervallo fra un comando di refresh e il successivo, non la finestra. Ma l'idea non cambia: una memoria che, per non dimenticare, deve ripetersi una quindicina di volte al secondo quello che sa. Un disco non ha bisogno di niente del genere.

Velocità. Qui i numeri fanno il lavoro da soli. Le righe sono numerate apposta, così i rimandi non ballano:

                          RAM (DRAM)      SSD NVMe        disco a piatti
  (1) capacita' tipica    8 - 16 GB       0,5 - 2 TB      1 - 4 TB
  (2) tempo di accesso    ~ 80 ns         ~ 50 us         ~ 8 ms
  (3) lo stesso, in s     0,000 000 08    0,000 05        0,008
  (4) quante volte
      piu' lenta          1               ~ 625           ~ 100 000
  (5) se togli corrente   perde tutto     ricorda         ricorda
Il conto della riga (4) è una divisione: 0,008 s ÷ 0,000 000 08 s = 100.000, e 0,000 05 ÷ 0,000 000 08 = 625. Fra la RAM e un disco a piatti non c'è una differenza di grado, ci sono cinque ordini di grandezza. Quegli 80 ns sono un valore tondo, scelto perché il rapporto si legga a occhio: nella sezione 7 troverai la RAM misurata su processori veri, fra 73 e 100 ns, e con quei valori i due rapporti vengono un po' diversi. È il punto della sezione 7, non una smentita di questa. Quel valore di circa 8 ms lo scomponiamo nella sezione 7, e va detto subito com'è fatto: per metà è aritmetica — mezzo giro di piatto a 7.200 giri al minuto, 4,17 ms, che si calcola in trenta secondi — e per metà è un dato meccanico di targa, il tempo di spostamento della testina, 4-8 ms, che dagli RPM non si ricava.

E l'esperimento costa zero. Spegni la macchina senza salvare. Quello che ritrovi era su disco, quello che è sparito era in RAM. Se «la memoria» fosse una sola, il documento non salvato sopravviverebbe.

La riga da tenere. «Memoria» in italiano è un sostantivo che fa due lavori. Ogni volta che lo scrivi in una verifica, aggiungi l'aggettivo: centrale o di massa. Metà degli errori di questo capitolo si fermano lì.

Come leggere questa guida

L'argomento non è un'aggiunta del tuo professore: sta scritto nei programmi, e i riferimenti qui sotto sono stati aperti uno per uno.

Negli istituti tecnici è «Architettura e componenti di un computer», voce delle Conoscenze di Tecnologie informatiche al primo biennio, con l'abilità «Riconoscere le caratteristiche funzionali di un computer (calcolo, elaborazione, comunicazione)». Le due frasi stanno nelle Linee guida per il primo biennio, Direttiva MIUR n. 57 del 15 luglio 2010, Allegato A.2 – Settore tecnologico. Il DPR 88/2010 va citato per il quadro orario, ed è il suo Allegato C a dare le 99 ore al primo anno: quel decreto ha solo quattro allegati, A, B, C e D, e un «Allegato A.2 del DPR 88/2010» semplicemente non esiste. Nel secondo biennio di Informatica e Telecomunicazioni l'argomento torna come prima voce delle Conoscenze di Sistemi e reti: «Struttura, architettura e componenti dei sistemi di elaborazione».

Il liceo scientifico opzione scienze applicate ha Informatica per 66 ore in ciascuno dei cinque anni (quadro orario, Allegato F al DPR 89/2010). Le Indicazioni nazionali per quel percorso — che sono un altro decreto, e per una coincidenza scomoda hanno anche loro un Allegato F: Allegato F al DM interministeriale 7 ottobre 2010 n. 211 — sono l'unico documento programmatico dell'informatica scolastica italiana in cui von Neumann compare per nome, e lo fanno così: «gli elementi funzionali della macchina di Von Neumann: CPU, memoria, dischi, bus e le principali periferiche». Nel liceo scientifico ordinario non c'è: dentro Matematica ci sono algoritmo, calcolabilità e rappresentazione dei dati, non l'architettura.

Alle medie non c'era nelle Indicazioni 2012, ed entra con le Nuove Indicazioni (Allegato al DM 9 dicembre 2025 n. 221, in Gazzetta Ufficiale il 27 gennaio 2026) a partire dall'a.s. 2026/27. Fra le competenze attese al termine della classe terza, voce Informatica, il testo chiede di «Comprendere a livello generale l'architettura di principio (fisica e funzionale) di un sistema informatico, le sue principali componenti hardware e software e i meccanismi fondamentali di Internet». Una nota per chi confronta i documenti e trova due versioni diverse: nella bozza del marzo 2025 la stessa frase diceva «di un sistema di elaborazione digitale», e l'espressione è stata cambiata in «sistema informatico» nel testo definitivo. È la versione definitiva quella che fa fede, e quella è la citazione qui sopra. Aggiungo che nelle Nuove Indicazioni von Neumann non è nominato: l'ho cercato nel testo, non c'è.

Un'onestà utile, perché in giro si legge il contrario: l'algebra di Boole non compare fra le conoscenze prescritte in nessuno dei tre documenti appena citati — l'ho cercata in tutti e tre, la parola non c'è. Si insegna dappertutto, ed è un prerequisito didattico reale, ma chi te la vende come obbligo di programma non ha una fonte.

Le sezioni da 1 a 4 e la 9 sono alla portata del primo biennio e del liceo scienze applicate. Le sezioni da 5 a 8 sono livello verifica di terza all'istituto tecnico. Le sezioni 10 e 11, più le domande frequenti in coda, servono la sera prima del compito.

Prerequisiti, che qui non rispieghiamo

Il binario e l'esadecimale stanno nella guida sui sistemi di numerazione: binario, decimale, esadecimale. Le porte AND, OR e NOT stanno in algebra di Boole e porte logiche. I diagrammi di flusso stanno in algoritmi e diagrammi di flusso. Questa guida è il punto in cui quei tre pezzi smettono di stare ognuno per conto suo e diventano una macchina che esegue davvero.

Pubblicità

Il modello di von Neumann: che cosa c'è davvero nel rapporto del 1945



Il documento esiste, si può leggere, e quasi nessuno lo apre. Si intitola First Draft of a Report on the EDVAC, porta la data del 30 giugno 1945 e nasce dal contratto W-670-ORD-4926 fra lo United States Army Ordnance Department e la Moore School of Electrical Engineering della University of Pennsylvania. Sul frontespizio c'è un nome solo: John von Neumann.

Le cinque parti, come le elenca il rapporto

La sezione 2.0, MAIN SUBDIVISIONS OF THE SYSTEM, enumera cinque parti, ognuna introdotta da un ordinale.

§Ordinale nel testoSiglaDenominazione originaleCome la chiami tu
2.2FirstCAcentral arithmetical partALU, unità aritmetico-logica
2.3SecondCCcentral controlunità di controllo
2.4-2.5ThirdMmemorymemoria centrale
2.7FourthIinputingresso
2.8FifthOoutputuscita

Sulla memoria vale la pena essere precisi, perché è l'unica delle cinque a stare a cavallo di due paragrafi: l'ordinale «Third» apre il § 2.4, e la frase che la battezza — the third specific part of the device: M — chiude il § 2.5.

Il § 2.6 fa due cose che valgono tutta la sezione. Primo, raggruppa CA e CC sotto un'unica sigla, C: è da lì che nasce la nozione moderna di CPU (central processing unit, unità centrale di elaborazione) — la CPU non è una delle cinque parti del rapporto, è la somma delle prime due. Secondo, introduce R, l'outside recording medium, che invece resta fuori dal conto.

   FUORI DAL DISPOSITIVO
   ------------------------------------------------------------
      R    outside recording medium                    (§ 2.6)
           schede perforate, nastro magnetico
   ------------------------------------------------------------
           v   R --> I                    ^   O --> R
   ============================================================ confine
           v                              ^
      +--------------------------+   +--------------------------+
         I   input      (§ 2.7)         O   output     (§ 2.8)
      +--------------------------+   +--------------------------+
           v   I --> M                    ^   M --> O
      +-----------------------------------------------------+
         M   memory     (§ 2.5)
      +-----------------------------------------------------+
           v   M --> C                    ^   C --> M
      +-----------------------------------------------------+
         C  =  CC   central control          (§ 2.3)
               CA   central arithmetical part (§ 2.2)
      +-----------------------------------------------------+

   frecce che NON esistono:    I --> C      C --> O      R --> C
Tre precisazioni che i manuali saltano quasi sempre. Due si leggono nello schema, la terza no, e dirlo è più onesto che far finta.

R non è una delle cinque parti. L'outside recording medium — schede, nastri — è introdotto nel § 2.6, ma sta fuori dal dispositivo e von Neumann non lo numera. Nello schema è sopra la riga del confine. Chi conta «sei parti», o chi mette R al posto di O, ha letto il riassunto di un riassunto.

I e O parlano con M, non con C. Il § 2.7 dice che è meglio fare tutti i trasferimenti da R verso M, e mai direttamente verso C; il § 2.8 dice la cosa simmetrica in uscita. Nello schema è l'ultima riga: le frecce I → C e C → O sono elencate fra quelle che non esistono.

Il bus non c'è, e questo nello schema non si vede — nessun disegno può mostrare un'assenza. Si verifica sul testo, ed è una verifica che puoi rifare tu: la parola bus nel rapporto non compare mai, e non compare mai nemmeno l'espressione «architettura di von Neumann». Elencare il bus fra le parti del modello è un errore storico: il bus è un'astrazione arrivata dopo, e lo incontrerai nella sezione 8 come concetto tecnico, non come citazione. Stessa storia per la parola «registro»: nel rapporto compare una volta sola, e come verbo. I nomi PC (program counter, il registro che tiene l'indirizzo della prossima istruzione) e IR (instruction register, il registro che tiene l'istruzione in corso), che incontrerai per esteso nella sezione 5, nel 1945 non ci sono. Arrivano dopo.

Una bozza incompiuta, e va detto

Il documento fondativo dell'informatica è un lavoro non finito. Michael D. Godfrey, che ne ha curato l'edizione critica per gli IEEE Annals of the History of Computing — la ristampa è al vol. 15, n. 4, 1993, pp. 27-75, DOI 10.1109/85.238389 — lo scrive nell'introduzione che accompagna quella ristampa, e sono quattro affermazioni sue, non del testo di von Neumann: il dattiloscritto non fu mai riletto; il testo rimanda in continuazione a sezioni mai scritte, e mancano proprio quella sulla programmazione e quella sull'input/output; i rinvii irrisolti riguardano le sezioni successive alla 15.0, dove il testo si ferma; il manoscritto autografo da cui fu battuto è stato cercato e non è mai stato ritrovato. (Un avvertimento se vai a cercarla: Godfrey nella sua stessa introduzione scrive «pp. 27-43», mentre il catalogo dell'editore dà 27-75. Succede, e quando due riferimenti non tornano fa fede l'indice del fascicolo, non la citazione di seconda mano.)

E c'è un dettaglio che rimette la cosa nella luce giusta. L'unico riferimento bibliografico dell'intero rapporto non è a un ingegnere: è a due neurofisiologi, W. S. McCulloch e W. Pitts, A logical calculus of the ideas immanent in nervous activity, in Bulletin of Mathematical Biophysics, vol. 5, 1943, pp. 115-133 — e nel dattiloscritto il cognome è storpiato in «MacCulloch», due volte, con perfino l'iniziale sbagliata la seconda volta. La sezione 4.0 si intitola ELEMENTS, SYNCHRONISM NEURON ANALOGY e la 6.0 introduce gli E-elements, elementi ideali modellati sul neurone. L'analogia di von Neumann è con il sistema nervoso, non con una macchina utensile. Quando il tuo libro scrive «la CPU è il cervello del computer», sta ripetendo senza saperlo una metafora vecchia di ottant'anni — che però nel rapporto era un'ipotesi di lavoro sui circuiti, non uno slogan.

E la paternità: il documento che ha fatto perdere il brevetto

È pacifico che il testo l'abbia scritto lui. È contestato che le idee fossero sue.

La Moore School ragionava sulla memorizzazione del programma circa sette mesi prima che von Neumann entrasse nel progetto: il 29 gennaio 1944 J. Presper Eckert mette per iscritto la Disclosure of a Magnetic Calculating Machine, che descrive una macchina elettronica capace di tenere programma e dati in memoria elettronica; von Neumann incontra Goldstine alla stazione di Aberdeen nell'estate del 1944 e vede l'ENIAC per la prima volta, con il nulla osta di sicurezza, il 7 settembre 1944. Sette mesi, dunque, non otto. Eckert e John Mauchly sostennero sempre che il concetto fosse emerso dalle riunioni del gruppo.

Fu Herman Goldstine, ufficiale di collegamento dell'Army, a far ciclostilare le note. Ventiquattro copie furono distribuite il 25 giugno 1945 alle persone legate al progetto EDVAC, con il solo nome di von Neumann in copertina; il rapporto porta invece in frontespizio la data del 30 giugno 1945, ed è quella che si cita come data del documento. Le due date convivono e non vanno fuse in una sola: quante copie siano circolate in tutto, poi, resta incerto. Il seguito è agli atti di un tribunale federale:

  brevetto ENIAC                US 3.120.606
  domanda depositata            26 giugno 1947   (Eckert e Mauchly)
  brevetto concesso             4 febbraio 1964
  causa                         Honeywell v. Sperry Rand
  giudice                       Earl R. Larson  (distretto del Minnesota)
  sentenza di annullamento      19 ottobre 1973
Attenzione alla data: diverse fonti secondarie riportano il 10 ottobre, ma la decisione è del 19 ottobre 1973. Fra i motivi dell'annullamento, la circolazione del First Draft fu qualificata come pubblicazione ai fini di legge, cioè arte nota più di un anno prima del deposito. In una riga, ed è la frase da portare all'orale: il documento che ha dato a von Neumann il nome dell'architettura è lo stesso che ha fatto perdere il brevetto a Eckert e Mauchly.

Un'ultima nota di terminologia, perché i libri divergono. Troverai «modello di von Neumann», «macchina di von Neumann» e «architettura di von Neumann» usati come sinonimi: lo sono. Nessuna delle tre espressioni compare nel rapporto del 1945.


Von Neumann o Harvard: una memoria o due (e perché il tuo PC è tutti e due)



La differenza che ti insegnano è «una memoria contro due». È vera e insufficiente: si ferma alla prima riga di una tabella che ne ha cinque, e la riga che conta davvero è l'ultima.

von NeumannHarvard (pura)
Memoriauna sola, condivisa fra istruzioni e datidue, fisicamente separate
Bus verso la memoriauno solodue indipendenti
Spazio di indirizzamentounicodistinto: l'indirizzo 0 delle istruzioni non è l'indirizzo 0 dei dati
Accesso simultaneoimpossibile: o l'istruzione o il datoistruzione e dato nello stesso ciclo, anche senza cache
Ampiezza delle paroledi fatto vincolata alla cella: un'istruzione più larga costa più accessilibera su ciascun lato: l'istruzione arriva in un colpo solo

   VON NEUMANN                        HARVARD (pura)

   +-----------------------+          +-----------+  +-----------+
      M   una sola memoria               Flash          SRAM
      istruzioni E dati                  istruzioni     dati
   +-----------------------+          +-----------+  +-----------+
             ^ v                           ^ v          ^ v
        un solo bus                   bus istruzioni  bus dati
             ^ v                           ^ v          ^ v
   +-----------------------+          +--------------------------+
             CPU                                 CPU
   +-----------------------+          +--------------------------+

   in un ciclo passa                  in un ciclo passano
   O l'istruzione O il dato           l'istruzione E il dato
L'ultima riga della tabella è quella che rende Harvard utile per davvero, ed è quella che quasi nessun manuale scolastico scrive — ma va detta con precisione, perché nella versione sbrigativa è falsa. Non è vero che «in una memoria unica un'istruzione più larga della cella non si può fare»: si può benissimo, si spalma su più celle consecutive, e la controprova sta nella sezione 8 di questa stessa guida, dove l'8088 legge otto bit per volta ed esegue istruzioni x86 lunghe parecchi byte. Quello che è vero è il costo: ogni prelievo diventa due o tre accessi invece di uno. Con memorie separate, invece, ciascuna ha la larghezza che le serve e l'istruzione arriva in un colpo solo. In un microcontrollore AVR a 8 bit i dati sono a 8 bit e le istruzioni a 16, e ognuna delle due memorie è dimensionata per il suo mestiere.

Harvard vera: i microcontrollori

Non è archeologia. I datasheet Microchip/Atmel degli AVR (ATmega32, ATmega128) lo dichiarano in prima pagina: architettura Harvard con memorie e bus separati per programma e dati, con il Flash del programma su un bus di indirizzi distinto da quello della SRAM (static random access memory, memoria statica: niente condensatore da rinfrescare, quindi veloce e cara — è di questa che sono fatte le cache) dei dati.

La conseguenza è misurabile e va detta con i numeri. Con un pipelining a un solo livello, mentre un'istruzione viene eseguita la successiva è già stata prelevata: si arriva così a un'istruzione per ciclo di clock. L'AVR ha inoltre 32 registri general purpose — non un accumulatore solo, come nei modelli didattici che userai nella sezione 6. Stesse scelte nei PIC e nei DSP.

È anche l'unico punto in cui i programmi ministeriali nominano esplicitamente questa roba: TPSIT del secondo biennio, articolazione Telecomunicazioni, chiede «Architettura e tecniche di programmazione dei microcontrollori e dei sistemi embedded».

Il nome è un'etichetta appiccicata dopo

Viene dall'Harvard Mark I (1944), che teneva le istruzioni su nastro perforato largo 24 bit e i dati in contatori elettromeccanici. Ma l'espressione «Harvard architecture» fu coniata decenni dopo, nel mondo dei microcontrollori, e applicata all'indietro a una macchina che non l'aveva mai sentita nominare. È esattamente la stessa situazione della sezione 2: «architettura di von Neumann» non è nel rapporto di von Neumann, «architettura Harvard» non è nei documenti dell'Harvard Mark I. Sono due nomi retrospettivi, e sapere che lo sono ti evita di trattarli come definizioni di legge.

E il computer su cui stai leggendo?

Né l'una né l'altra. ARM, Power ISA e x86 sono Harvard modificate, e le varianti sono tre.

1) Split cache, la più diffusa, ed è quella del tuo PC. C'è un solo spazio di indirizzamento — quindi verso il programmatore il modello resta von Neumann, un indirizzo è un indirizzo e basta — ma la CPU preleva le istruzioni dalla cache istruzioni e i dati dalla cache dati. Sono le due righe L1i e L1d che trovi etichettate così nelle tabelle della sezione 7: il parallelismo Harvard esiste, ma solo ai livelli bassi della gerarchia.
2) Memoria programma leggibile come dati: spazi separati, ma istruzioni speciali permettono di leggere il Flash come se fosse un dato (per le costanti iniziali) o di riscriverlo. È il caso tipico dei microcontrollori.
3) Memoria dati eseguibile come programma: per esempio i Maxim MAXQ, dove si può eseguire da qualunque segmento, ma lo stesso segmento non può essere letto come dato mentre lo si esegue.

Un esempio concreto della prima variante mescolata alla seconda: nei Cortex-M i bus sono separati — bus ICode verso il Flash per le istruzioni, system bus per i dati — ma accedono a un unico spazio di memoria, e un arbitro consente l'accesso alla memoria istruzioni da entrambi i bus, non però nello stesso momento. Separati nei percorsi, uniti nell'indirizzamento, non simultanei.

La frase da portare all'interrogazione. Nessun PC moderno è von Neumann in senso stretto e nessuno è Harvard in senso stretto: è von Neumann verso il programma — spazio di indirizzamento unico — e Harvard verso il silicio — percorsi separati in L1. Chi risponde «il mio PC è di von Neumann» dice una cosa vera al livello di astrazione del programmatore e falsa al livello del progettista, e la parte importante della risposta è dichiarare a quale livello si sta parlando.
Sui nomi, un'ultima avvertenza. Alcuni testi italiani chiamano «architettura Harvard» qualunque macchina con L1i e L1d separate, altri riservano il termine solo agli spazi di indirizzamento distinti e per i PC usano sempre «Harvard modificata». La seconda è più precisa, la prima la incontrerai lo stesso: se in verifica scrivi «Harvard modificata» e spieghi perché, sei coperto in entrambi i casi.


Il collo di bottiglia: che cosa disse davvero Backus (e che cosa non disse)



Questa espressione ha un autore, una data, una rivista e una pagina. È raro, in informatica, e conviene approfittarne.

John Backus riceve l'ACM Turing Award del 1977, consegnato alla ACM Annual Conference di Seattle il 17 ottobre. La sua Turing Lecture esce l'anno dopo con il titolo Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs, in Communications of the ACM, vol. 21, n. 8, agosto 1978, pp. 613-641 (DOI 10.1145/359576.359579). Il passaggio che ci interessa apre la sezione 3, Von Neumann Computers, a pagina 615, e l'ho letto sul testo pubblicato.

Lì Backus riduce il calcolatore a tre pezzi soli: una CPU, uno store — la memoria — e un tubo di collegamento capace di trasmettere una sola parola per volta fra i due, oltre a mandare un indirizzo alla memoria. E poi scrive la frase, che nell'originale suona così: «I propose to call this tube the von Neumann bottleneck».

Il ragionamento che i manuali tagliano

Quello che segue è la parte buona, e sta tutta in tre passaggi.

Primo. Il compito di un programma è cambiare in modo sostanziale il contenuto della memoria. Ma questo cambiamento deve avvenire pompando singole parole avanti e indietro attraverso quel tubo. Un programma che modifica un milione di celle deve far passare un milione di parole, una alla volta, nello stesso identico condotto.

Secondo, ed è l'osservazione che nessuno riporta. Backus la apre con un avverbio che vale tutto, ironically: gran parte di quel traffico non è dato utile, sono nomi di dati, cioè indirizzi, più le operazioni che servono soltanto a calcolare indirizzi. E c'è un regresso: prima che una parola possa passare nel tubo, il suo indirizzo deve già essere nella CPU. O ci è arrivato dalla memoria — e allora serviva a sua volta un indirizzo — oppure è stato generato dalla CPU con una regola fissa, e l'esempio che fa lui è proprio «aggiungi 1 al program counter».

Terzo, ed è il punto vero. Per Backus non è soltanto un collo di bottiglia fisico per il traffico dei dati: è soprattutto un collo di bottiglia intellettuale, che ci ha tenuti legati al pensiero una parola per volta invece di spingerci a ragionare per unità concettuali più grandi. Programmare, conclude, è in gran parte pianificare e dettagliare il traffico di parole attraverso quel condotto — e buona parte di quel traffico non riguarda i dati, ma dove trovarli. Da qui la proposta dell'articolo, che è la programmazione funzionale.

Il tubo, con i numeri di una singola istruzione

Prendi l'istruzione ADD 201 della macchina didattica che trovi tracciata riga per riga nella sezione 6: somma all'accumulatore il contenuto della cella 201. Ecco che cosa attraversa il tubo, nell'ordine, per una sola istruzione:

  che cosa passa nel tubo                    che cos'e' davvero
  -----------------------------------------  -----------------------
  1.   0x011     CPU -----> memoria          un indirizzo  (un nome)
  2.   0x20C9    memoria -> CPU              l'istruzione
  3.   0x0C9     CPU -----> memoria          un indirizzo  (un nome)
  4.   0x000A    memoria -> CPU              IL DATO

  quattro transiti, una parola per volta, sullo stesso bus
  dati utili trasportati:  1 su 4
Due interrogazioni alla memoria per una sola somma: una per l'istruzione, una per l'operando. E dei quattro passaggi, due sono indirizzi e uno è l'istruzione: un solo transito su quattro porta il numero che stai davvero sommando. È letteralmente l'ironia di Backus, contata a mano.

Ed è anche la ragione per cui l'architettura Harvard della sezione 3 ha senso — ma attenzione a quali transiti si sovrappongono, perché è facile dire la cosa sbagliata. I passaggi 3-4 non possono avvenire in parallelo con i passaggi 1-2 della stessa istruzione: l'indirizzo dell'operando si conosce solo dopo aver ricevuto e decodificato l'istruzione, quindi dipendono l'uno dall'altro. Il parallelismo vero è un altro: con due bus, il prelievo dell'istruzione successiva — i passaggi 1-2 della prossima — può sovrapporsi all'accesso al dato di quella in corso, i passaggi 3-4 di questa. È esattamente il pipelining a un livello dell'AVR visto nella sezione 3. Il collo di bottiglia non sparisce, si sdoppia.

L'errore da non commettere, ed è comodissimo da fare

Backus non stava parlando della lentezza della RAM rispetto alla CPU.

Quel problema esiste, ha un nome — memory wall — e nasce dagli anni Novanta in poi, quando la frequenza dei processori cresce molto più in fretta dei tempi di accesso della DRAM. Nel 1977 il divario di velocità era modesto, e la cache non era l'oggetto centrale che è oggi. Backus stava facendo un'argomentazione sul linguaggio e sul modello di pensiero, non una misura di latenza: attribuirgli il memory wall è un anacronismo di quindici anni buoni.

Perché te lo dico invece di lasciar perdere. Perché la confusione è comoda: «collo di bottiglia = la RAM è lenta» è una frase che funziona, si ricorda e in un tema di sistemi sembra giusta. E infatti circola dappertutto. Se in verifica ti chiedono che cos'è il collo di bottiglia di von Neumann, la risposta completa ha due tempi: il limite strutturale è il bus unico condiviso fra istruzioni e dati, e il termine lo conia Backus nel 1978 parlando di modelli di programmazione; la lentezza della memoria rispetto al processore è un altro problema, si chiama memory wall, e la risposta dell'industria a quello è la gerarchia delle cache della sezione 7.
Le due cose si toccano, però, ed è giusto dirlo: quando la RAM diventa cento volte più lenta della CPU, il tubo di Backus smette di essere solo un limite concettuale e diventa anche un limite di prestazioni misurabile. Il conto lo trovi svolto nell'esercizio 11 della sezione 10: se il 40 per cento del tempo di un programma è attesa di memoria, raddoppiare il clock non raddoppia niente, e il tetto massimo si calcola in due righe.


Dentro la CPU: unità di controllo, ALU, registri (e i nomi che cambiano fra i libri)



Prima di aprire la scatola, togliamo di mezzo la frase che fa più danni di tutte: «la CPU è il cervello del computer». È un'analogia, non una descrizione, e installa esattamente l'idea sbagliata — che lì dentro ci sia un posto dove stanno i programmi. Dentro una CPU la memoria si misura in centinaia di byte. Su un x86-64 i sedici registri general purpose da 64 bit fanno 16 × 8 = 128 byte in tutto: rispetto a un programma da 1 MB, cioè 1.000.000 di byte, sono 128 ÷ 1.000.000 = lo 0,0128 per cento. Il resto è altrove, e la CPU se lo va a prendere un pezzo alla volta.

Avvertenza sulle sigle, e serve subito. Da qui in avanti PC significa program counter, il registro che tiene l'indirizzo della prossima istruzione. La stessa sigla però è anche quella del personal computer, e in quel senso la ritroverai apposta più avanti: «entra nel tuo PC» nella sezione 7, «il primo IBM PC» nella sezione 8, due volte nell'errore 8 della sezione 11. Non è un capriccio di questa guida: è una collisione reale che trovi in tutti i libri, e l'unico rimedio è il contesto — dentro una traccia del ciclo macchina è sempre il contatore di programma, in una frase su un computer da tavolo non lo è mai. Quando in un compito scrivi PC, dichiara di quale dei due stai parlando.
I pezzi sono tre.

1. L'unità di controllo, e il paletto che regge tutto

L'unità di controllo (CU, control unit) discende direttamente dalla CC del rapporto EDVAC. Il suo lavoro è decodificare l'istruzione e generare i segnali verso tutto il resto: verso l'ALU, verso i registri, verso la memoria.

Su questo punto von Neumann pianta un paletto che è il nocciolo del programma memorizzato, e che i manuali quasi sempre saltano. Il § 2.3 distingue due cose: le istruzioni specifiche del problema, che vanno memorizzate come qualunque altro dato, e gli organi generali di controllo che le fanno eseguire, che sono circuiti fisici saldati una volta per tutte. Solo i secondi sono la CC.

Perché conta? Perché è la ragione per cui la stessa identica macchina fisica esegue oggi un videogioco e domani un foglio di calcolo, senza che tu cambi un filo. I fili sanno fare sempre le stesse dieci o cento cose; a cambiare è la sequenza di numeri che sta in memoria. Se questa distinzione non è chiara, tutto il resto della guida resta un elenco di sigle.

2. L'ALU, che non è una scatola nera

L'ALU (Arithmetic Logic Unit) discende dalla CA del rapporto. Von Neumann prevedeva le quattro operazioni +, −, × e ÷, e nel § 2.2 discuteva già se estenderla a radice quadrata, logaritmi e seno oppure restringerla togliendo la divisione.

E qui la guida sull'algebra di Boole smette di essere teoria: l'ALU è fatta delle porte AND, OR e NOT di algebra di Boole e porte logiche, e il full adder costruito lì — somma con lo XOR, riporto con l'AND, otto in fila per sommare due byte — è letteralmente il circuito che eseguirà l'istruzione ADD della prossima sezione. I flag Z (risultato zero) e C (riporto in uscita) non sono un argomento nuovo: sono le uscite di quel circuito, portate fuori e conservate in un registro. Nella sezione 6 li vedrai in due colonne della tabella di traccia.

3. I registri, e cosa li distingue dalla memoria

I registri sono memorie interne alla CPU, le più veloci della macchina. Ma la differenza vera con la RAM non è la velocità: è che i registri non hanno un indirizzo. Alla cella 200 ci arrivi mettendo il numero 200 sul bus indirizzi; all'accumulatore ci arrivi perché la CU ha un filo che va lì. Si raggiungono per nome, non per numero. Chi in un compito scrive che i registri sono «memoria di primo livello indirizzabile» ha già perso mezzo punto.

E un registro non contiene «un numero»: contiene una parola binaria di larghezza fissa. Il valore 0x10C8 che troverai fra poco è 0001 0000 1100 1000, e quei sedici bit si spezzano in 4 di codice operativo più 12 di indirizzo. È la lettura per gruppi di quattro bit della guida sui sistemi di numerazione: binario, decimale, esadecimale, ed è tutta la ragione per cui il contenuto dei registri si scrive in esadecimale e mai in decimale: in esadecimale i confini fra i campi si vedono a occhio, in decimale spariscono.

La tabella dei nomi, perché i libri non vanno d'accordo

Questa è la tabella che serve davvero, e che quasi nessuna pagina italiana mette.

FunzioneSigla principaleAltri nomi documentatiIn italiano
Indirizzo della prossima istruzionePC, program counterIP instruction pointer (terminologia Intel), IAR instruction address register, instruction counter nella documentazione IBM del 1953contatore di programma, puntatore all'istruzione
Istruzione in corsoIR, instruction registerCIR current instruction register, standard nella didattica britannicaregistro istruzione, registro istruzione corrente
Indirizzo della cella da leggere o scrivereMAR, memory address registerregistro indirizzi di memoria
Dato in transito da e verso la memoriaMDR, memory data registerMBR memory buffer registerregistro dati di memoria, registro di transito
Operando e risultato dell'ALUACC, accumulatorA, ACaccumulatore

Più due che compaiono in ogni CPU vera: il registro di stato o dei flag (status register, PSW, SR) e lo stack pointer (SP).

Le due avvertenze che valgono un voto

MDR e MBR sono la stessa cosa: due nomi per un registro solo, e se il tuo libro usa l'uno e il compagno di banco l'altro non state studiando due macchine diverse.

MAR e MDR, invece, non sono affatto sinonimi fra loro — ed è l'errore che si vede più spesso nei compiti. Insieme formano l'interfaccia minima verso la memoria: l'uno dice dove, l'altro porta cosa. Sono anche larghi in modo diverso: il MAR è largo quanto il bus indirizzi, il MDR quanto la cella. Nella macchina della prossima sezione sono rispettivamente 12 e 16 bit, e già solo contando le linee si capisce chi è chi.

Ultima onestà, che nessuno dice. L'accumulatore singolo è un modello didattico, non una CPU vera. L'x86-64 ha 16 registri general purpose da 64 bit — la sigla che troverai nella sezione 7 è GPR, general purpose registers; l'8086 del 1978 ne aveva otto da 16 bit più i registri di segmento, l'instruction pointer e il registro dei flag; un microcontrollore AVR a 8 bit ne ha 32. Presentare l'accumulatore come il registro operativo di un processore moderno è falso. Presentarlo come il modello su cui si impara il ciclo macchina è corretto — ed è esattamente quello che facciamo adesso.


Il ciclo macchina passo per passo: la traccia di esecuzione, riga per riga



Questa è la sezione centrale della guida, ed è il pezzo che manca ovunque. Le altre pagine liquidano fetch, decode ed execute in due frasi a fase; nessuna segue un'istruzione vera con valori veri fino in fondo. Qui la seguiamo.

La notazione è destinazione ← sorgente: la freccia va da dove il dato è a dove finisce. Nei blocchi incolonnati la scrivo <-, perché la freccia unicode manda in vacca l'allineamento.

Le due convenzioni di questo modello, dichiarate prima di cominciare. Prima: ogni micro-operazione della tabella dura esattamente un ciclo di clock, riga di decodifica compresa. Non è una legge di natura, è la convenzione che rende i conti rifacibili — e senza dichiararla il calcolo dei cicli non sta in piedi. Seconda: contiamo il costo di un'istruzione in CPI, cicli per istruzione, che è il numero di righe di tabella che quell'istruzione occupa. La formula completa in cui il CPI entra la trovi nella sezione 9.

Le tre fasi, in ordine

FETCH (prelievo), quattro micro-operazioni.

1) MAR ← PC — il PC contiene l'indirizzo della prossima istruzione, e viene copiato nel MAR. Il PC non si svuota: copiare non è spostare.
2) MDR ← Memoria[MAR] — la memoria risponde, e la parola torna nel MDR viaggiando sul bus dati.
3) PC ← PC + 1 — tieni d'occhio questo passo, ci torniamo fra tre righe.
4) IR ← MDR — l'istruzione passa nel registro istruzione, dove codice operativo e operando possono finalmente essere separati.

E qui la nota che molti libri numerano come se fosse un passo a sé, sbagliando i conti di tutto il capitolo: contemporaneamente al passo 2 l'indirizzo esce sul bus indirizzi e la CU alza il segnale READ sul bus di controllo. Non è un ciclo in più: è ciò che rende possibile il passo MDR ← Memoria[MAR]. Se lo conti come quinto passo, l'istruzione ti costa 9 cicli invece di 8, il programma 27 invece di 24, e tutti i tempi finali saltano.

DECODE (decodifica), una micro-operazione. La CU legge l'IR e separa il codice operativo dal campo indirizzo. Poi manda i segnali: all'ALU, se c'è da calcolare; di nuovo alla memoria, se c'è da prelevare un operando.

EXECUTE (esecuzione), i cinque casi di questa macchina. Prima di scrivere questa parte, una nota di notazione, perché il simbolo è nuovo: IR[11..0] significa i dodici bit di destra dell'IR, numerati da 11 (il più a sinistra dei dodici) fino a 0 (l'ultimo a destra). Sono il campo indirizzo, e l'opcode resta fuori.

LOAD indirizzoMAR ← IR[11..0], poi MDR ← Memoria[MAR], poi ACC ← MDR. Tre cicli. Aggiorna il flag Z.
ADD indirizzoMAR ← IR[11..0], poi MDR ← Memoria[MAR], poi ACC ← ACC + MDR. Tre cicli, due accessi alla memoria per una sola istruzione. Aggiorna Z e C.
STORE indirizzoMAR ← IR[11..0], poi MDR ← ACC, poi Memoria[MAR] ← MDR con il segnale WRITE. Tre cicli. Non tocca i flag.
JMP indirizzoPC ← IR[11..0], e basta. Un ciclo, nessun accesso alla memoria, nessun flag toccato.
JZ indirizzo (salto condizionato) → se Z = 1 allora PC ← IR[11..0]; se Z = 0 non si scrive niente e il PC resta quello già incrementato nel fetch. Un ciclo in entrambi i casi.

Il passo 3 decide se hai capito il ciclo macchina o no

Il PC si incrementa durante il fetch, prima dell'esecuzione, e non alla fine del ciclo.

Non è un dettaglio di ordine: serve esattamente perché un salto, in fase di esecuzione, possa sovrascrivere il valore appena incrementato. Se il PC venisse incrementato dopo l'esecuzione, ogni JMP scriverebbe la destinazione nel PC e un istante dopo l'incremento la distruggerebbe: i salti non funzionerebbero, e con loro non funzionerebbero i cicli, le condizioni, le chiamate a funzione. Cioè: niente.

Se di questa pagina ti porti via una riga sola, portati via questa. E più avanti la vedrai succedere davvero, al passo 36 della traccia.

La macchina su cui tracciamo

Parole da 16 bit, bus indirizzi a 12 bit, 4.096 celle da 16 bit ciascuna. Formato dell'istruzione: 4 bit di codice operativo più 12 di indirizzo.

  codice   nome    che cosa fa                              cicli
  ------   -----   --------------------------------------   -----
  0001     LOAD    ACC <- M[indirizzo]                        8
  0010     ADD     ACC <- ACC + M[indirizzo]                  8
  0011     STORE   M[indirizzo] <- ACC                        8
  0100     JMP     PC  <- indirizzo                           6
  0101     JZ      se Z=1  PC <- indirizzo, altrimenti nulla  6

  flag:  Z = 1 quando il valore appena scritto nell'ACC e' 0x0000
         C = riporto in uscita dell'ultima ADD
         LOAD e ADD aggiornano Z;  solo ADD aggiorna C;
         STORE, JMP e JZ non toccano nessuno dei due.
         All'accensione ACC = 0x0000, quindi Z = 1 e C = 0.
Stato iniziale: PC = 0x010, ACC = 0x0000, Z = 1, C = 0, M[0x010] = 0x10C8, M[0x0C8] = 0x0025.

Prima di tracciare si decodifica a mano. Sempre.

0x10C8   in binario:   0001   0000 1100 1000
                       ^^^^   ^^^^^^^^^^^^^^
                       opcode     indirizzo

opcode      0001             = 1        ->  LOAD
indirizzo   0000 1100 1000   = 0x0C8    =   200

Istruzione:  LOAD 200   ->   ACC <- contenuto della cella 200

La traccia di una sola istruzione

Ogni riga mostra lo stato dopo il passo. I trattini indicano contenuto non significativo.

Passo  Micro-operazione                  PC     IR      MAR    MDR     ACC     Z  C
-----  --------------------------------  -----  ------  -----  ------  ------  -  -
0      (stato iniziale)                  0x010  ----    ----   ----    0x0000  1  0
1      MAR <- PC                         0x010  ----    0x010  ----    0x0000  1  0
2      MDR <- M[MAR]                     0x010  ----    0x010  0x10C8  0x0000  1  0
3      PC <- PC + 1                      0x011  ----    0x010  0x10C8  0x0000  1  0
4      IR <- MDR                         0x011  0x10C8  0x010  0x10C8  0x0000  1  0
5      decodifica opcode 0001 = LOAD     0x011  0x10C8  0x010  0x10C8  0x0000  1  0
6      MAR <- IR[11..0]                  0x011  0x10C8  0x0C8  0x10C8  0x0000  1  0
7      MDR <- M[MAR]                     0x011  0x10C8  0x0C8  0x0025  0x0000  1  0
8      ACC <- MDR                        0x011  0x10C8  0x0C8  0x0025  0x0025  0  0
Fetch ai passi 1-4, decode al 5, execute ai 6-8. Alla fine ACC = 0x0025, cioè 37 in decimale; PC = 0x011, cioè punta già all'istruzione successiva; IR = 0x10C8, cioè conserva l'istruzione in corso. E guarda la colonna Z: era 1 perché l'accumulatore era vuoto, al passo 8 diventa 0 perché adesso dentro c'è 37. Costo: 8 cicli, quindi CPI = 8 per questa istruzione.

Due errori da evitare qui. Al passo 6 non si scrive MAR ← IR: nel MAR vanno solo i 12 bit di indirizzo, l'opcode resta all'unità di controllo. E il MAR è largo 12 bit, quindi i 16 bit dell'IR non ci starebbero nemmeno fisicamente.

Il programma di tre istruzioni

Stessa macchina. In memoria:

Indirizzo  Contenuto  Significato              Codifica
---------  ---------  -----------------------  ----------------------
0x010      0x10C8     LOAD  200                opcode 0001, ind. 0x0C8
0x011      0x20C9     ADD   201                opcode 0010, ind. 0x0C9
0x012      0x30CA     STORE 202                opcode 0011, ind. 0x0CA
0x0C8      0x0025     dato: 37
0x0C9      0x000A     dato: 10
0x0CA      0x0000     spazio per il risultato
Passo  Micro-operazione                  PC     IR      MAR    MDR     ACC     Z  C
-----  --------------------------------  -----  ------  -----  ------  ------  -  -
0      (stato iniziale)                  0x010  ----    ----   ----    0x0000  1  0
       --- ISTRUZIONE 1: LOAD 200 ---
1      MAR <- PC                         0x010  ----    0x010  ----    0x0000  1  0
2      MDR <- M[MAR]                     0x010  ----    0x010  0x10C8  0x0000  1  0
3      PC <- PC + 1                      0x011  ----    0x010  0x10C8  0x0000  1  0
4      IR <- MDR                         0x011  0x10C8  0x010  0x10C8  0x0000  1  0
5      decodifica 0001 = LOAD            0x011  0x10C8  0x010  0x10C8  0x0000  1  0
6      MAR <- IR[11..0]                  0x011  0x10C8  0x0C8  0x10C8  0x0000  1  0
7      MDR <- M[MAR]                     0x011  0x10C8  0x0C8  0x0025  0x0000  1  0
8      ACC <- MDR                        0x011  0x10C8  0x0C8  0x0025  0x0025  0  0
       --- ISTRUZIONE 2: ADD 201 ---
9      MAR <- PC                         0x011  0x10C8  0x011  0x0025  0x0025  0  0
10     MDR <- M[MAR]                     0x011  0x10C8  0x011  0x20C9  0x0025  0  0
11     PC <- PC + 1                      0x012  0x10C8  0x011  0x20C9  0x0025  0  0
12     IR <- MDR                         0x012  0x20C9  0x011  0x20C9  0x0025  0  0
13     decodifica 0010 = ADD             0x012  0x20C9  0x011  0x20C9  0x0025  0  0
14     MAR <- IR[11..0]                  0x012  0x20C9  0x0C9  0x20C9  0x0025  0  0
15     MDR <- M[MAR]                     0x012  0x20C9  0x0C9  0x000A  0x0025  0  0
16     ACC <- ACC + MDR                  0x012  0x20C9  0x0C9  0x000A  0x002F  0  0
       --- ISTRUZIONE 3: STORE 202 ---
17     MAR <- PC                         0x012  0x20C9  0x012  0x000A  0x002F  0  0
18     MDR <- M[MAR]                     0x012  0x20C9  0x012  0x30CA  0x002F  0  0
19     PC <- PC + 1                      0x013  0x20C9  0x012  0x30CA  0x002F  0  0
20     IR <- MDR                         0x013  0x30CA  0x012  0x30CA  0x002F  0  0
21     decodifica 0011 = STORE           0x013  0x30CA  0x012  0x30CA  0x002F  0  0
22     MAR <- IR[11..0]                  0x013  0x30CA  0x0CA  0x30CA  0x002F  0  0
23     MDR <- ACC                        0x013  0x30CA  0x0CA  0x002F  0x002F  0  0
24     M[MAR] <- MDR                     0x013  0x30CA  0x0CA  0x002F  0x002F  0  0
Verifica dell'aritmetica al passo 16: 0x0025 + 0x000A = 37 + 10 = 47 = 0x002F. Corretto, e siccome 47 non è zero e non c'è riporto oltre il sedicesimo bit, Z e C restano 0. E la memoria alla fine: M[0x0CA] = 0x002F, dove prima c'era 0x0000.

Tre cose da guardare, e sono il motivo per cui la tabella vale più della spiegazione.

Primo: il fetch è identico tutte e tre le volte, passi 1-4, 9-12, 17-20. Cambia solo l'execute. Il ciclo macchina non è una procedura diversa per ogni istruzione: è sempre la stessa testa con code diverse.

Secondo: al passo 9 il MDR contiene ancora 0x0025, lasciato lì dalla LOAD. I registri non si azzerano fra un'istruzione e l'altra, e chi nella traccia scrive dei trattini a ogni cambio di istruzione sta descrivendo una macchina che non esiste.

Terzo: nella STORE il MDR gira al contrario. Nella LOAD e nella ADD si riempie dalla memoria; qui si riempie dalla CPU (passo 23) e si svuota verso la memoria (passo 24). Chi scrive MDR ← M[MAR] anche nella STORE cancella il risultato appena calcolato. E chi salta il passo 23 scrivendo direttamente Memoria[MAR] ← ACC sta collegando l'accumulatore al bus dati, cosa che nel disegno non c'è.

Costo di queste tre istruzioni: 24 cicli, quindi CPI medio 24 ÷ 3 = 8. A 100 MHz il periodo di clock è T = 1 diviso 100 × 10⁶ Hz = 10 ns, quindi questa parte dura 24 × 10 ns = 240 ns.

E adesso i salti, che sono il pezzo che nessuno traccia

Il programma non è finito. Nelle due celle successive c'è questo:

Indirizzo  Contenuto  Significato              Codifica
---------  ---------  -----------------------  ----------------------
0x013      0x5016     JZ    0x016              opcode 0101, ind. 0x016
0x014      0x4010     JMP   0x010              opcode 0100, ind. 0x010
Si riparte dallo stato lasciato dal passo 24, senza azzerare niente.

Passo  Micro-operazione                  PC     IR      MAR    MDR     ACC     Z  C
-----  --------------------------------  -----  ------  -----  ------  ------  -  -
24     (stato lasciato dalla STORE)      0x013  0x30CA  0x0CA  0x002F  0x002F  0  0
       --- ISTRUZIONE 4: JZ 0x016 ---
25     MAR <- PC                         0x013  0x30CA  0x013  0x002F  0x002F  0  0
26     MDR <- M[MAR]                     0x013  0x30CA  0x013  0x5016  0x002F  0  0
27     PC <- PC + 1                      0x014  0x30CA  0x013  0x5016  0x002F  0  0
28     IR <- MDR                         0x014  0x5016  0x013  0x5016  0x002F  0  0
29     decodifica 0101 = JZ              0x014  0x5016  0x013  0x5016  0x002F  0  0
30     Z = 0  ->  nessuna scrittura      0x014  0x5016  0x013  0x5016  0x002F  0  0
       --- ISTRUZIONE 5: JMP 0x010 ---
31     MAR <- PC                         0x014  0x5016  0x014  0x5016  0x002F  0  0
32     MDR <- M[MAR]                     0x014  0x5016  0x014  0x4010  0x002F  0  0
33     PC <- PC + 1                      0x015  0x5016  0x014  0x4010  0x002F  0  0
34     IR <- MDR                         0x015  0x4010  0x014  0x4010  0x002F  0  0
35     decodifica 0100 = JMP             0x015  0x4010  0x014  0x4010  0x002F  0  0
36     PC <- IR[11..0]                   0x010  0x4010  0x014  0x4010  0x002F  0  0
Guarda il PC ai passi 33 e 36. Al passo 33 il fetch lo ha portato a 0x015, che è la cella dopo la JMP. Al passo 36 l'esecuzione ci scrive sopra 0x010, e il valore incrementato sparisce senza aver mai servito a niente. È il salto che sovrascrive il PC, esattamente il meccanismo di cui parlavamo prima: se l'incremento arrivasse dopo, al passo 37 il PC tornerebbe a 0x011 e il salto sarebbe cancellato. Con questa istruzione il programma ricomincia da 0x010, e hai un ciclo infinito. Non è però il più corto scrivibile: con questo stesso set il più corto è una sola JMP che punta al proprio indirizzo, una istruzione e 6 cicli. Questo qui ne gira cinque, e costa 36 cicli a ogni giro.

E la JZ del passo 30 non ha saltato perché Z valeva 0. Ecco il confronto, sullo stesso passo 30, con il solo Z cambiato:

                                  caso reale       caso alternativo
  come ci si arriva               ACC = 0x002F     ACC = 0x0000
                                  (37 + 10 = 47)   (se in 0x0C8 e in
                                                   0x0C9 ci fosse 0x0000)
  flag Z prima del passo 30       0                1
  passo 30                        nessuna          PC <- IR[11..0]
                                  scrittura
  PC dopo il passo 30             0x014            0x016
  prossima istruzione prelevata   quella in 0x014  quella in 0x016
Costo dei salti: 6 cicli ciascuno, quattro di fetch, uno di decodifica, uno di esecuzione. Nessun accesso alla memoria in fase di esecuzione, ed è per questo che costano meno di LOAD, ADD e STORE.

Il conto totale del programma completo, e questo è il numero da portare a casa:

  3 istruzioni da 8 cicli  =  24
  2 istruzioni da 6 cicli  =  12
  totale                   =  36 cicli per 5 istruzioni
  CPI medio                =  36 : 5  =  7,2
  a 100 MHz  (T = 10 ns)   =  36 x 10 ns  =  360 ns
Nota che il CPI medio non è una proprietà del processore: è il risultato del miscuglio di istruzioni che il programma esegue. Tre LOAD-ADD-STORE e due salti danno 7,2; un programma fatto di soli salti darebbe 6. È la ragione per cui nella sezione 9 il CPI compare come fattore a sé, accanto al numero di istruzioni.

Qui si agganciano gli algoritmi

Questa tabella è la tabella di traccia della guida su algoritmi e diagrammi di flusso, applicata ai registri della macchina invece che alle variabili del tuo pseudocodice. Stesso strumento, un livello più in basso: una colonna per ogni cosa che può cambiare, una riga per ogni passo, e lo stato scritto dopo.

E il rombo del diagramma di flusso, il blocco di decisione con le sue due uscite V e F, qui smette di essere un simbolo e diventa una cosa fisica: è la JZ del passo 30, cioè un valore scritto nel PC invece che nel PC incrementato, e la condizione è un bit di flag. La freccia all'indietro del ciclo è la JMP del passo 36. Il diagramma non descrive la macchina, descrive quello che la macchina esegue. E il motivo per cui può eseguirlo è il passo 3.

Avvertenza, e va detta prima che qualcuno ti corregga all'orale. Questo è il modello sequenziale. I processori reali usano pipeline, esecuzione superscalare, fuori ordine e speculativa: perfino un microcontrollore AVR o un Cortex-M preleva già l'istruzione successiva mentre esegue la corrente. Il modello a passi resta lo strumento didattico giusto — è il solo con cui puoi scrivere una traccia — ma va dichiarato come modello, non spacciato per fotografia.
Pubblicità

La gerarchia delle memorie: i numeri veri, misurati su processori con un nome



Qui non ci sono stime né ordini di grandezza vaghi. Ci sono misure su processori identificati, e se un numero non ha un processore accanto non serve a niente.

Intel Core i7-1065G7 (Ice Lake, 10 nm, fino a 3,9 GHz)

LivelloCapacitàLatenza misurata
L1d (dati)48 KB5 cicli
L2512 KB13 cicli
L38 MB42 cicli
RAM LPDDR4-373342 cicli più 87 ns

Quell'ultima riga si legge così, ed è istruttiva: un accesso alla RAM costa prima i 42 cicli spesi a cercare invano nella L3, e poi altri 87 ns. A 3,9 GHz un ciclo dura 1 diviso 3,9 × 10⁹ Hz = 0,256 ns, quindi 42 cicli sono 10,8 ns e il totale è circa 98 ns. Un miss non è gratis: paghi anche la ricerca fallita.

Nota sull'unità, perché in questa guida distinguiamo: i costruttori scrivono KB e MB, ma per le cache l'unità è sempre binaria. I 48 KB di L1d sono 48 KiB, cioè 48 × 1.024 = 49.152 byte.

AMD Ryzen 9 7950X (Zen 4, boost 5,7 GHz)

LivelloCapacitàLatenza misurata
L1i (istruzioni) e L1d (dati)32 KB ciascuna, per core4 cicli, circa 0,7 ns
L21 MB per core, il doppio dei 512 KB di Zen 314 cicli, circa 2,5 ns
L332 MB per CCD (core complex die, il gruppo di core inciso su un unico chiplet), e il 7950X ne ha due: quindi 64 MBcirca 8-9 ns
RAM DDR5-600073,35 ns

Quella prima riga è anche la risposta al rimando della sezione 3: L1i è la cache istruzioni, L1d la cache dati, e il fatto che siano due oggetti distinti sullo stesso livello è il punto in cui l'architettura Harvard entra nel tuo PC.

Controlla tu il primo numero, è la verifica che vale più di tutte: a 5,7 GHz il periodo è 1 diviso 5,7 × 10⁹ = 0,175 ns, e 4 × 0,175 = 0,70 ns. Torna. Fai lo stesso con la L3: 8 ns diviso 0,175 ns dà circa 46 cicli, non 9. Se trovi in giro «L3 di Zen 4: 9 cicli», qualcuno ha scambiato i nanosecondi per cicli.

Agli estremi della scala: i registri costano circa 1 ciclo e sull'x86-64 i GPR (general purpose registers, i registri di uso generale) sono in tutto 128 byte; un SSD NVMe sta a 20-70 µs in lettura casuale; un disco meccanico a 5-10 ms.

Il numero dell'hard disk si rifà in trenta secondi

Questo conto vale la pena farlo in classe, perché mostra da dove esce il numero invece di farlo imparare a memoria — e mostra anche quale metà del numero si calcola e quale no.

Un disco a 7.200 giri al minuto compie 7.200 ÷ 60 = 120 giri al secondo, quindi un giro completo dura 1 ÷ 120 = 8,33 ms. Attenzione: quegli 8,33 ms sono la durata del giro, non il tempo di accesso, e confonderli con gli «8 ms» della tabella della sezione 1 è un classico. Il tempo di accesso si compone di due pezzi:

  latenza rotazionale media   mezzo giro  =  8,33 : 2   =  4,17 ms   <- aritmetica
  tempo di ricerca (seek)     spostamento della testina =  4-8 ms    <- dato meccanico
  ---------------------------------------------------------------
  tempo di accesso            somma dei due             =  5-10 ms
La prima riga si calcola dai giri al minuto: in media, quando la testina è già sulla traccia giusta, il settore che cerchi è a mezzo giro di distanza. La seconda non si calcola dagli RPM: è il tempo che il braccio impiega a spostarsi fra le tracce, sta sui 4-8 ms per i dischi da 3,5 pollici, e lo leggi sulla scheda tecnica. Il totale è 5-10 ms, ed è tutto meccanica, non elettronica.

La scala che rende l'idea

I nanosecondi non dicono niente a nessuno. Trasformiamoli — ma dichiarando il clock di riferimento, altrimenti la scala non è verificabile. Prendiamo un processore a 3 GHz: un ciclo dura un terzo di nanosecondo, che si scrive 0,33 ns, e diciamo che quel ciclo duri un secondo. Tieni a mente la forma esatta, perché i cicli si contano moltiplicando i nanosecondi per 3: se dividi per lo 0,33 arrotondato ti escono 303 cicli invece di 300, e ti sembra di aver sbagliato tu.

   livello     tempo reale     cicli (ns x 3)      scala "1 ciclo = 1 s"
   --------    ------------    ----------------    ---------------------
   registro    ~ 0,33 ns              1            1 secondo
   cache L1    ~ 1,3 ns               4            4 secondi
   cache L2    ~ 4,7 ns              14            14 secondi
   cache L3    ~ 15 ns               45            45 secondi
   RAM         ~ 100 ns             300            5 minuti
   SSD NVMe    ~ 50 us          1,5 x 10^5         1,7 giorni
   HDD         ~ 8 ms           2,4 x 10^7         278 giorni
I due conti in fondo, che sono quelli che quasi tutte le versioni in giro sbagliano: 50 µs sono 50.000 ns, e × 3 = 1,5 × 10⁵ cicli; poi 1,5 × 10⁵ secondi ÷ 86.400 = 1,7 giorni. Allo stesso modo 8 ms sono 8.000.000 ns, e × 3 = 2,4 × 10⁷ cicli, e 2,4 × 10⁷ secondi ÷ 86.400 = 278 giorni, cioè poco più di nove mesi. Fra prendere un dato da un registro e prenderlo da un disco meccanico c'è lo stesso rapporto che c'è fra un secondo e un anno scolastico intero, ricreazioni comprese.

Ed è questa la ragione per cui esiste la cache. Non è un accessorio: è l'unico modo di non pagare i 5 minuti tutte le volte. Ed è anche il motivo per cui una percentuale di miss apparentemente ridicola pesa moltissimo — un hit rate del 95 per cento, che sembra ottimo, può più che raddoppiare il tempo medio di accesso. Il conto lo fai per esteso nell'esercizio 8.

Due avvertenze di onestà

La tabella di Jeff Dean è datata sulla parte storage. La celeberrima Latency Numbers Every Programmer Should Know (L1 = 0,5 ns, L2 = 7 ns, RAM = 100 ns, seek del disco = 10 ms) circola ovunque, e per cache e RAM regge ancora benissimo. Ma i suoi valori per la memoria di massa risalgono a circa il 2012: la riga «lettura casuale di 4 KB da SSD = 150 µs» è vecchia di un ordine di grandezza, visto che gli NVMe attuali stanno a 20-70 µs. Se la citi, dillo. Citare un numero senza la sua data è il modo più elegante di sbagliare.

«L1, L2, L3» è già una semplificazione in movimento. Nei P-core Lion Cove degli Intel Core Ultra 200S (Arrow Lake) c'è un livello in più: L0 dati da 48 KB, poi L1 dati da 192 KB, L1 istruzioni da 64 KB, L2 da 3 MB per P-core (4 MB condivisi per ogni modulo di quattro E-core) e L3 fino a 36 MB. La gerarchia si allunga; la logica no. Il principio resta quello del conto qui sopra: più è veloce, meno ce n'è, e più vicino sta.


I bus e il conto 2ⁿ: quanta memoria posso indirizzare



Tre linee, tre mestieri diversi. Confonderli è la premessa di metà degli errori negli esercizi.

BusCosa trasportaVersoChe cosa decide
Bus datiil contenuto delle cellebidirezionale, si legge e si scrivequanti bit si trasferiscono per ogni accesso
Bus indirizzil'indirizzo della cellaunidirezionale, dalla CPU alla memoriaquanta memoria è indirizzabile
Bus di controlloREAD, WRITE, clock, interrupt, readymistose si legge o si scrive, e quando

Il bus di controllo sembra il meno importante e non lo è. Torna alla traccia della sezione 6: il passo 7 e il passo 24 mettono sul bus indirizzi lo stesso tipo di numero e sul bus dati lo stesso tipo di parola. A distinguere una lettura da una scrittura è soltanto la linea di controllo. Senza di lei la memoria riceve un indirizzo e non sa che farsene.

La regola, con la parola giusta

Ogni linea del bus indirizzi porta un bit, e ogni bit raddoppia le combinazioni. Con n linee le combinazioni distinte sono 2ⁿ, numerate da 0 a 2ⁿ − 1, e ciascuna individua una cella.

Celle. Non byte. La dimensione totale è 2ⁿ × ampiezza della cella, e fa 2ⁿ byte solo quando la cella è di un byte. È l'errore singolo più frequente di tutto il capitolo, e nasce dall'aver memorizzato la formula sbagliata.

È la stessa potenza di due della guida sui sistemi di numerazione: binario, decimale, esadecimale: ogni linea in più raddoppia gli indirizzi possibili esattamente come ogni cifra binaria in più raddoppia i numeri rappresentabili. Non è un'analogia, è lo stesso conto applicato al rame.

n    celle indirizzabili = 2^n  con celle da 1 byte  macchina reale
--   -------------------------  -------------------  -------------------------------
10                       1.024  1 KiB
16                      65.536  64 KiB               8080, Z80, 6502
20                   1.048.576  1 MiB                Intel 8086 e 8088 (1978)
24                  16.777.216  16 MiB               Intel 80286
32               4.294.967.296  4 GiB                limite dei sistemi a 32 bit
48         281.474.976.710.656  256 TiB              x86-64, forma canonica
57     144.115.188.075.855.872  128 PiB              x86-64, paginazione a 5 livelli

L'esempio che va fatto per intero

Un esercizio da verifica suona così: bus indirizzi a 24 bit, celle da 16 bit, quanta memoria?

   celle      = 2^24                     = 16.777.216 celle
   ampiezza   = 16 bit = 16 / 8          = 2 byte per cella
   totale     = 16.777.216 x 2 byte      = 33.554.432 byte
              = 33.554.432 / 1.024       = 32.768 KiB
              = 32.768 / 1.024           = 32 MiB     <-- non 16 MiB
Chi risponde 16 MiB ha letto 2²⁴ e si è fermato lì. La stessa trappola, girata al contrario, sta nella macchina della sezione 6: 4.096 celle da 16 bit fanno 4.096 × 2 = 8.192 byte = 8 KiB, non 4 KiB.

La riga dei 20 bit merita il suo paragrafo

L'Intel 8086, lanciato l'8 giugno 1978 a 5 MHz, aveva bus dati a 16 bit e bus indirizzi a 20 bit. Venti linee, dunque 2²⁰ = 1.048.576 byte = 1 MiB di spazio fisico indirizzabile. E qui viene il punto: il famigerato tetto di 1 MB del DOS non è una scelta di software, non è una svista dei programmatori dell'epoca e non è pigrizia di Microsoft. È una conseguenza aritmetica diretta della larghezza di un fascio di fili. Sopra quel numero non c'era niente da programmare, perché non c'era nessun indirizzo da scrivere.

E la controprova sta nel modello accanto. L'8088 — quello del primo IBM PC — ha gli stessi 20 bit di indirizzo e solo 8 bit di dato esterno. Indirizza esattamente la stessa memoria, 1 MiB, e la trasferisce alla metà della velocità: le istruzioni x86 lunghe più di un byte gli costano più accessi, il che è precisamente l'argomento del costo che hai visto nella sezione 3. Il bus dati cambia la banda, non lo spazio. Se in un esercizio ti danno tutti e due i numeri e tu usi quello del bus dati per calcolare la capacità, hai risposto a una domanda che nessuno ti aveva fatto.

Il chiarimento che quasi tutti sbagliano

Un processore «a 64 bit» non ha 64 linee di indirizzo, e nessun processore esistente indirizza 2⁶⁴ byte.

L'architettura x86-64 impone la forma canonica: gli indirizzi virtuali validi vanno da 0 fino a 00007FFF FFFFFFFF, e poi riprendono da FFFF8000 00000000 in su. Cioè 48 bit realmente implementati, per 256 TiB di spazio virtuale utilizzabile. Con la paginazione a cinque livelli introdotta da Intel si sale a 57 bit, cioè 128 PiB. Sul lato fisico AMD64 arriva a 48 bit, con l'architettura predisposta per 52 bit (4 PiB); le prime implementazioni si fermavano a 40 bit, cioè 1 TiB.

Perché non cablarle tutte e 64? Perché ogni linea di indirizzo costa piedini, piste, consumo e transistor nelle tabelle delle pagine, e 256 TiB sono già più memoria di quanta ne esista in un data center. Si implementa quello che serve.

La lezione da portarsi via è più larga del numero: «a 64 bit» è una dichiarazione architetturale, non una misura di quello che il chip ha davvero dentro. Separare il modello dall'implementazione è, con l'incremento del PC durante il fetch, la seconda cosa che distingue chi ha capito questa materia da chi l'ha imparata a memoria.


Il clock: perché i gigahertz non misurano la velocità



Se c'è un numero che tutti sanno recitare di un processore, è quello: 3,5 GHz. Ed è il numero da cui parte l'unica frase che ti fa perdere punti in ogni verifica su questo argomento: «il mio è da 3,5 GHz, quindi è più veloce del tuo da 2,8». La frase è sbagliata, ma non è stupida: è vera dentro una famiglia di processori, e chi la dice sta generalizzando un confronto legittimo a un caso in cui non vale più. Smontiamola partendo da che cosa sia fisicamente il clock.

Un metronomo, non un motore

Il segnale nasce da un oscillatore al quarzo, che produce un'onda a frequenza fissa; un circuito elettronico la trasforma in onda quadra, cioè in una successione netta di 0 e 1; un moltiplicatore ne genera un multiplo fisso; e la rete di distribuzione del clock la porta a tutti i componenti che devono restare sincronizzati fra loro.

Quello che il clock non fa è far lavorare la CPU. Non spinge niente: scandisce l'istante in cui i risultati sono considerati stabili. Fra un fronte e il successivo i segnali si assestano nei circuiti, e al fronte dopo vengono letti. È un metronomo, non un motore — e i musicisti non suonano più forte perché il metronomo va più veloce, suonano più in fretta finché ci riescono.

Frequenza e periodo: uno l'inverso dell'altro

La relazione è T = 1/f, e va saputa nei due versi.

FrequenzaPeriodo T = 1/f
5 MHz (Intel 8086, 1978)200 ns
100 MHz (la macchina della sezione 6)10 ns
1 GHz1 ns
3 GHz0,33 ns
5,7 GHz0,175 ns

E adesso il confronto che rende quei numeri qualcosa di reale. In 0,33 ns la luce nel vuoto percorre circa 10 cm (3 × 10⁸ m/s × 3,33 × 10⁻¹⁰ s ≈ 0,1 m). Un segnale in una pista di rame viaggia a circa metà o due terzi di quella velocità, quindi ne fa 5-7 cm.

Rileggi la riga: a 3 GHz, in un solo ciclo di clock, un segnale non attraversa nemmeno tutta la scheda madre. La dimensione fisica del chip non è più un dettaglio meccanico, è diventata un vincolo elettrico. Ed è la prima ragione per cui la frequenza non può salire all'infinito.

L'equazione che sostituisce il confronto fra gigahertz

Il tempo di esecuzione di un programma non dipende da un fattore, ma da tre:

Tempo CPU = numero di istruzioni × CPI × durata del ciclo di clock
• il numero di istruzioni dipende dal programma, dal compilatore e dal set di istruzioni — cioè, a monte, dall'algoritmo che hai scelto, quello della guida su algoritmi e diagrammi di flusso;
• il CPI (cicli per istruzione, la sigla che hai già usato nella sezione 6) dipende dalla microarchitettura — pipeline, unità di esecuzione, cache, predizione dei salti — e anche dal miscuglio di istruzioni del programma, come hai visto quando i due salti hanno portato il CPI medio da 8 a 7,2;
• solo la durata del ciclo è l'inverso della frequenza.

Il clock è uno dei tre fattori, e non è detto che sia il dominante. Chi confronta due processori guardando i gigahertz sta confrontando un terzo dell'equazione e assumendo che gli altri due siano uguali.

Da qui il corollario che va detto per esteso, perché quasi nessuno lo dice: una CPU a 3 GHz non esegue tre miliardi di istruzioni al secondo. Con CPI 1,5 ne esegue 3 × 10⁹ ÷ 1,5 = due miliardi; una CPU superscalare, che ritira più di un'istruzione per ciclo, può superarne tre. Il clock conta i cicli, non le istruzioni.

Il mito dei megahertz è un fatto datato, non un'opinione

Non è una tesi da manuale: è un episodio con una data e un palco. Keynote di Steve Jobs al Macworld Expo di New York, 18 luglio 2001. Un PowerPC G4 a 867 MHz completa un compito in 45 secondi; un Pentium 4 a 1,7 GHz impiega 82 secondi. Clock 1,96 volte più alto (1700 ÷ 867), tempo 1,82 volte maggiore (82 ÷ 45). Da lì in avanti il fenomeno ha un nome, megahertz myth, e fu Apple stessa a battezzarlo dal palco.

La regola operativa che ne segue: il confronto per frequenza è valido solo fra processori della stessa famiglia e dello stesso progetto. Un 80486 a 50 MHz è davvero circa il doppio di un 80486 a 25 MHz. Un ARM e un x86 alla stessa frequenza non si possono confrontare affatto.

E perché i gigahertz si sono fermati: è fisica

La soglia dei 3 GHz viene raggiunta nel 2002. Poi la corsa si ferma, e non per scelta commerciale.

Nel maggio 2004 Intel cancella Tejas e Jayhawk, i successori del Pentium 4. Il motivo è documentato: un campione Tejas a 90 nm dissipava 150 W a soli 2,8 GHz, contro gli ~84 W di un Prescott alla stessa frequenza. L'obiettivo dichiarato per Tejas nel 2003 era 7 GHz o più: non fu mai raggiunto, e il progetto morì. Dietro c'è la fine dello scaling di Dennard, intorno al 2005: rimpicciolire i transistor smette di ridurre proporzionalmente il consumo. È il power wall, il muro della potenza. La risposta dell'industria fu cambiare strada: microarchitetture più efficienti e, soprattutto, più core.

Ecco la frase da portarti in verifica, ed è tutta aritmetica:

Dal 1978 al 2002 la frequenza dei processori è cresciuta circa 600 volte (da 5 MHz a 3 GHz: 3000 ÷ 5 = 600). Dal 2002 a oggi, sì e no 2 volte: si sta nella fascia 4-6 GHz, e l'estremo alto diviso il punto di partenza, 6 ÷ 3, fa esattamente 2. Eppure i calcolatori sono enormemente più veloci. Tutto il guadagno degli ultimi vent'anni viene da CPI, cache, parallelismo e numero di core: cioè esattamente dai fattori che i gigahertz non misurano.

Dodici esercizi svolti passo passo



Le convenzioni si dichiarano una volta sola, in testa, e valgono per tutti e dodici. Metà degli «errori dello studente» su questo argomento sono in realtà esercizi in cui la convenzione non era scritta da nessuna parte. Qui i prefissi si applicano soltanto: la loro costruzione sta nella guida sui sistemi di numerazione: binario, decimale, esadecimale.

1 KB = 10^3 B = 1.000 B           1 KiB = 2^10 B = 1.024 B
1 MB = 10^6 B = 1.000.000 B       1 MiB = 2^20 B = 1.048.576 B
1 GB = 10^9 B                     1 GiB = 2^30 B = 1.073.741.824 B

b = bit (minuscolo)    B = byte (maiuscolo)    1 B = 8 b

2^1  = 2        2^8  = 256      2^15 = 32.768     2^20 = 1.048.576
2^2  = 4        2^9  = 512      2^16 = 65.536     2^24 = 16.777.216
2^3  = 8        2^10 = 1.024    2^17 = 131.072    2^30 = 1.073.741.824
2^4  = 16       2^11 = 2.048    2^18 = 262.144    2^32 = 4.294.967.296
2^5  = 32       2^12 = 4.096    2^19 = 524.288
2^6  = 64       2^13 = 8.192
2^7  = 128      2^14 = 16.384

Esercizi 1 e 2 — Dal bus indirizzi alla memoria (e ritorno)

Testo 1. Bus indirizzi a 16 linee: quante celle? Se ogni cella è 1 byte, quanti KiB? E quante linee per indirizzare 1.048.576 celle? E per 1.000.000?

La mossa che sblocca. Ogni linea porta un bit e ogni bit raddoppia le combinazioni: con n linee gli indirizzi distinti sono 2ⁿ, numerati da 0 a 2ⁿ − 1.

• 2¹⁶ = 65.536 celle; 65.536 B ÷ 1.024 = 64 KiB
• 1.048.576 = 2²⁰ → 20 linee
• per 1.000.000: 2¹⁹ = 524.288 non basta, 2²⁰ = 1.048.576 sì → 20 linee, con 1.048.576 − 1.000.000 = 48.576 indirizzi di avanzo

Errore tipico. Scrivere 16 × 2 = 32 (si eleva, non si moltiplica), oppure arrotondare per difetto a 19 linee «perché è più vicino». Se le celle non ci stanno tutte, il bus non serve: si arrotonda sempre per eccesso.

Testo 2. Bus indirizzi a 20 bit, celle da 16 bit: dimensione totale? E quante linee per 512 KiB con celle da 8 bit?

La mossa che sblocca. Sono due numeri indipendenti che si moltiplicano: totale = 2ⁿ × ampiezza della cella. Il bus indirizzi dice quante celle, l'ampiezza dice quanto è larga ciascuna.

celle    = 2^20 = 1.048.576
ampiezza = 16 b = 2 B
totale   = 1.048.576 x 16 b = 16.777.216 b
         = 1.048.576 x  2 B =  2.097.152 B
controllo: 16.777.216 b : 8 = 2.097.152 B   coincide
in KiB:  2.097.152 : 1.024 = 2.048 KiB
in MiB:  2.048 : 1.024 = 2 MiB
512 KiB = 512 × 1.024 = 524.288 B; celle da 1 B → 524.288 celle = 2¹⁹ → 19 linee.

Errore tipico. Fermarsi a 2²⁰ e rispondere «1 MiB». 2ⁿ conta le celle, non i byte: sono 1 MiB solo se la cella è da un byte.

Esercizio 3 — Decimale contro binario

Testo. Un disco venduto «500 GB»: quanti GiB mostra il sistema operativo? E un file da 2.500.000 B in MB, KiB, MiB?

La mossa che sblocca. Non esiste una «conversione fra KB e KiB»: si torna sempre al numero grezzo di byte e poi si divide per il fattore giusto. È lo stesso meccanismo dei prefissi della guida sui sistemi di numerazione, applicato qui a capacità di memoria.

500 GB = 500 x 10^9 = 500.000.000.000 B
1 GiB  = 2^30       =   1.073.741.824 B
500.000.000.000 : 1.073.741.824 = 465,66 GiB
465,66 : 500 = 0,9313  ->  il 6,87 % in meno

2.500.000 B : 10^6       = 2,5 MB
2.500.000 B : 1.024      = 2.441,40625 KiB
2.500.000 B : 1.048.576  = 2,384 MiB
Errore tipico. Dividere 500 per 1,024 una volta sola (viene 488,3) invece che per 1,024³ = 1,073741824. Il fattore va applicato tante volte quanti sono i gradini di prefisso. Secondo errore: concludere che il disco sia difettoso. Terzo errore, ed è quello della sezione 1: dividere una capacità decimale per una binaria senza convertirle prima entrambe in byte.

Esercizi 4 e 5 — Clock, CPI, MIPS

Testo 4. Ciclo di clock a 2,5 GHz? E frequenza di un processore con ciclo da 1,25 ns?

T = 1 : (2,5 x 10^9) = 0,4 x 10^-9 s = 0,4 ns = 400 ps
f = 1 : (1,25 x 10^-9) = 0,8 x 10^9 Hz = 800 MHz = 0,8 GHz
controllo: 800 x 10^6 x 1,25 x 10^-9 = 1   torna
Errore tipico. Rispondere «2,5 ns», cioè ricopiare il numero invece del reciproco. Controllo di verso: più il clock è alto, più il ciclo è corto.

Testo 5. Processore a 3 GHz, CPI medio 4. Istruzioni al secondo, durata media di un'istruzione, tempo per 6 × 10⁹ istruzioni.

Due sigle, sciolte qui perché servono da adesso in poi: IPS sta per istruzioni per secondo, MIPS per milioni di istruzioni per secondo. Sono un tasso, non un tempo.

IPS = 3 x 10^9 : 4 = 7,5 x 10^8 istr/s = 750 MIPS
T_istr = 4 x 0,3333 ns = 1,3333 ns
T = 6 x 10^9 : 7,5 x 10^8 = 8 s
controllo: 6 x 10^9 x 4 x 3,3333 x 10^-10 = 8 s
Errore tipico. Moltiplicare per il CPI invece di dividere. Controllo di buon senso: con CPI maggiore di 1 le istruzioni al secondo sono sempre meno degli hertz.

Esercizi 6 e 7 — Le due tracce, da rifare a mano

Sono le tracce della sezione 6. Qui vanno rifatte sul quaderno a colonne chiuse, e poi confrontate. Ricorda la convenzione: una micro-operazione, un ciclo di clock, decodifica compresa.

Testo 6. Macchina didattica: parole da 16 bit, bus indirizzi a 12 bit, formato 4 bit di opcode più 12 di indirizzo (0001 = LOAD, 0010 = ADD, 0011 = STORE, 0100 = JMP, 0101 = JZ). PC = 0x010, ACC = 0x0000, Z = 1, C = 0, M[0x010] = 0x10C8, M[0x0C8] = 0x0025. Traccia PC, IR, MAR, MDR, ACC, Z e C.

Prima si decodifica a mano:

0x10C8 = 0001 0000 1100 1000
         ^^^^ ^^^^^^^^^^^^^^
         op        indirizzo
opcode 0001 = LOAD
indirizzo 0000 1100 1000 = 0x0C8 = 200      ->   LOAD 200
Poi la traccia. Ogni riga mostra lo stato dopo il passo. Ricorda che IR[11..0] sono i dodici bit di destra dell'IR, cioè il campo indirizzo.

Passo  Micro-operazione        PC     IR      MAR    MDR     ACC     Z  C
-----  ----------------------  -----  ------  -----  ------  ------  -  -
0      stato iniziale          0x010  ----    ----   ----    0x0000  1  0
1      MAR <- PC               0x010  ----    0x010  ----    0x0000  1  0
2      MDR <- M[MAR]           0x010  ----    0x010  0x10C8  0x0000  1  0
3      PC <- PC + 1            0x011  ----    0x010  0x10C8  0x0000  1  0
4      IR <- MDR               0x011  0x10C8  0x010  0x10C8  0x0000  1  0
5      decodifica 0001 = LOAD  0x011  0x10C8  0x010  0x10C8  0x0000  1  0
6      MAR <- IR[11..0]        0x011  0x10C8  0x0C8  0x10C8  0x0000  1  0
7      MDR <- M[MAR]           0x011  0x10C8  0x0C8  0x0025  0x0000  1  0
8      ACC <- MDR              0x011  0x10C8  0x0C8  0x0025  0x0025  0  0
Fetch = passi 1-4, decode = 5, execute = 6-8. 8 cicli, quindi CPI = 8 per questa istruzione. ACC finale = 0x0025 = 37, e Z passa da 1 a 0.

Errore tipico. Al passo 6 scrivere MAR <- IR, copiando tutti i 16 bit: nel MAR vanno solo i 12 bit di indirizzo, l'opcode resta all'unità di controllo. E incrementare il PC alla fine invece che al passo 3. Terzo errore: contare cinque passi di fetch perché si è numerato «l'indirizzo esce sul bus e la CU alza READ» come un passo a sé. Non lo è: accade insieme al passo 2, e contarlo porta a 9 cicli invece di 8.

Testo 7. Stessa macchina, cinque istruzioni: LOAD 200 (0x10C8), ADD 201 (0x20C9), STORE 202 (0x30CA), JZ 0x016 (0x5016), JMP 0x010 (0x4010), con M[0x0C8] = 0x0025 e M[0x0C9] = 0x000A. Traccia tutto e calcola la durata a 100 MHz.

Il fetch è identico per tutte e cinque, quattro passi: cambia solo l'execute. E i registri non si azzerano fra un'istruzione e l'altra. I punti di controllo:

Passo  Micro-operazione        PC     IR      MAR    MDR     ACC     Z  C
-----  ----------------------  -----  ------  -----  ------  ------  -  -
8      ACC <- MDR              0x011  0x10C8  0x0C8  0x0025  0x0025  0  0
16     ACC <- ACC + MDR        0x012  0x20C9  0x0C9  0x000A  0x002F  0  0
23     MDR <- ACC              0x013  0x30CA  0x0CA  0x002F  0x002F  0  0
24     M[MAR] <- MDR           0x013  0x30CA  0x0CA  0x002F  0x002F  0  0
30     Z=0 -> nessun salto     0x014  0x5016  0x013  0x5016  0x002F  0  0
33     PC <- PC + 1            0x015  0x5016  0x014  0x4010  0x002F  0  0
36     PC <- IR[11..0]         0x010  0x4010  0x014  0x4010  0x002F  0  0
Verifica dell'aritmetica al passo 16: 0x0025 + 0x000A = 37 + 10 = 47 = 0x002F. Stato finale della memoria: M[0x0CA] = 0x002F. E confronta i passi 33 e 36: il fetch aveva portato il PC a 0x015, il salto ci scrive sopra 0x010. È lì che si vede perché l'incremento sta nel fetch.

prime 3 istruzioni:  24 cicli    CPI = 24 : 3 = 8
i due salti:         12 cicli    CPI = 12 : 2 = 6
tutto il programma:  36 cicli    CPI medio = 36 : 5 = 7,2

T_clock a 100 MHz = 1 : (100 x 10^6) = 10 ns
T (3 istruzioni)  = 24 x 10 ns = 240 ns
T (5 istruzioni)  = 36 x 10 ns = 360 ns
Errore tipico. Nella STORE scrivere M[MAR] <- ACC saltando il passo 23: l'accumulatore non è collegato al bus dati, deve passare per il MDR. Attenzione al verso: nella LOAD e nella ADD il MDR si riempie dalla memoria, nella STORE si riempie dalla CPU. E terzo: dare 8 cicli anche ai salti. Un salto non tocca la memoria in fase di esecuzione, quindi costa 6.

Esercizio 8 — Tempo medio di accesso con la cache

Testo. Cache con tempo di accesso 2 ns, penalità aggiuntiva di 60 ns in caso di miss, hit rate 95%. Tempo medio? E quale hit rate per scendere a 4 ns?

La mossa che sblocca. La penalità qui è aggiuntiva: anche quando si sbaglia, la cache è stata comunque interrogata e quei 2 ns si pagano lo stesso.

miss rate = 1 - 0,95 = 0,05
t_medio = 2 + (0,05 x 60) = 2 + 3 = 5 ns
controprova con la media pesata:
   0,95 x 2 + 0,05 x 62 = 1,9 + 3,1 = 5 ns    coincide

2 + m x 60 <= 4   ->   m <= 2 : 60 = 0,03333
hit rate >= 96,67 %
Errore tipico, e vale metà esercizio. Scrivere 0,95 × 2 + 0,05 × 60 = 4,9 ns, dimenticando che il miss paga anche i 2 ns della cache. Ma attento: se il testo dicesse «in caso di miss l'accesso richiede 60 ns in tutto», allora 4,9 ns sarebbe la risposta giusta. Prima di calcolare, stabilisci quale delle due convenzioni usa il testo e scrivila sul foglio.

Esercizio 9 — Vince il processore col clock più basso

Testo. Stesso programma di 2 × 10⁹ istruzioni. CPU A: 3,0 GHz, CPI 5. CPU B: 2,4 GHz, CPI 3.

A: T_clock = 0,3333 ns   5 x 0,3333 = 1,6667 ns/istr   600 MIPS
B: T_clock = 0,4167 ns   3 x 0,4167 = 1,2500 ns/istr   800 MIPS

A: 2 x 10^9 : 6,0 x 10^8 = 3,3333 s
B: 2 x 10^9 : 8,0 x 10^8 = 2,5000 s
rapporto = 3,3333 : 2,5 = 1,3333
Vince B, 1,33 volte più veloce, con il clock più basso. Il vantaggio di CPI (5 ÷ 3 = 1,667) supera lo svantaggio di clock (3,0 ÷ 2,4 = 1,25): 1,667 ÷ 1,25 = 1,333.

Errore tipico. Rispondere «A, ha più GHz». E poi l'asimmetria: B impiega il 25% di tempo in meno (0,8333 ÷ 3,3333), A il 33,3% in più. Guadagni e perdite percentuali non si invertono cambiando segno.

Esercizio 10 — Velocità di un bus

Testo. Bus dati largo 32 bit a 200 MHz, un trasferimento per ciclo. Velocità in MB/s, e tempo per 16 MiB.

larghezza = 32 b : 8 = 4 B
velocita' = 4 B x 200 x 10^6 = 800 x 10^6 B/s = 800 MB/s
                             = 800.000.000 : 1.048.576 = 762,94 MiB/s

16 MiB = 16 x 1.048.576 = 16.777.216 B
t = 16.777.216 : 800.000.000 = 0,02097152 s = 20,97 ms

controprova per cicli:
16.777.216 : 4 = 4.194.304 trasferimenti
T_clock = 5 ns   ->   4.194.304 x 5 = 20.971.520 ns = 20,97 ms
Errore tipico. Moltiplicare 32 × 200 × 10⁶ e scrivere «6,4 GB/s»: quelli sono bit al secondo, cioè 6,4 Gb/s = 800 MB/s. Il fattore 8 è l'errore numero uno della materia. Secondo errore: dividere 16 MiB per 800 MB/s mescolando le due unità.

Esercizio 11 — Perché alzare solo il clock non basta

Testo. Un programma dura 100 s: 60 s di calcolo nella CPU, 40 s di attese di memoria e I/O che non dipendono dal clock. Raddoppiando il clock? Quadruplicandolo? E il massimo teorico?

La mossa che sblocca. Si accelera solo la parte accelerabile: T_nuovo = T_accelerabile / k + T_fisso. È la legge di Amdahl in versione aritmetica.

k = 2   ->  60 : 2 + 40 = 70 s    speedup = 100 : 70 = 1,43
k = 4   ->  60 : 4 + 40 = 55 s    speedup = 100 : 55 = 1,82
k -> inf ->   0    + 40 = 40 s    speedup = 100 : 40 = 2,5
Raddoppiando ancora il clock, da 2× a 4×, il tempo scende solo da 70 a 55 secondi. E anche con un processore infinitamente veloce il programma non scenderà mai sotto i 40 s. È questo il conto promesso in fondo alla sezione 4.

Errore tipico. Applicare il fattore a tutto il tempo (100 ÷ 2 = 50 s). Ed è precisamente il ragionamento con cui si compra la CPU più costosa e non si vede alcuna differenza. Morale: se il 40% del tempo è attesa di memoria, si investe in cache e in bus, non in megahertz.

Esercizio 12 — Riconoscere i blocchi dallo schema

Testo. Assegna a ogni lettera il nome corretto, giustificando con l'indizio dello schema, non con la memoria. Poi: quanto vale in KiB la memoria (J)? E che cosa manca del tutto?

   DENTRO LA CPU                                  FUORI
   -------------------------------------------    ----------------------
   (A) ------------> (F)                          (J)  4096 celle x 16 bit
   (G) ------------> (B)
   (G) <-----------> (D)
   (G) ------------> (C)
   (B) ------------> (E)
   (D) <-----------> (C)
   (E) ------------> tutti gli altri

   (F)  ====(H) 12 linee, un verso solo=====>     (J)
   (G)  <===(I) 16 linee, doppio verso======>     (J)
Indizi: (A) contiene 0x0012 e aumenta di 1 dopo ogni prelievo. (B) contiene 0x20C9, una parola intera, e i suoi primi 4 bit vanno a (E). (C) esegue +, −, AND, OR e produce i flag Z e C. (D) contiene 0x002F ed è l'unico registro collegato in entrambi i versi a (C) — a (C) arriva anche (G), ma in un verso solo. (E) riceve i primi 4 bit di (B) ed emette segnali verso tutti gli altri. (F) e (G) sono gli unici registri affacciati fuori dalla CPU.

LetteraNomeIndizio decisivo
(A)PC, contatore di programmacontiene un indirizzo, si incrementa da solo a ogni fetch, e alimenta (F)
(B)IR, registro istruzionericeve la parola-istruzione intera da (G) e la spezza in opcode più indirizzo
(C)ALUesegue le operazioni e genera i flag
(D)Accumulatoreunico registro dati legato all'ALU nei due versi
(E)Unità di controllolegge l'opcode e pilota tutti gli altri
(F)MARaffacciato su un fascio unidirezionale di 12 linee, alimentato da (A)
(G)MDRaffacciato sul fascio bidirezionale largo quanto la cella, e alimenta (B)
(H)Bus indirizzi12 linee, un solo verso: CPU verso memoria
(I)Bus dati16 linee, doppio verso: lettura e scrittura
(J)Memoria centralematrice di celle indirizzabili

Le due frecce che nello schema fanno tutto il lavoro sono (A) → (F) e (G) → (B), e conviene rileggerle accanto alla traccia della sezione 6: sono i passi 1 e 4 del fetch. Il PC non alimenta mai direttamente l'IR — se in uno schema trovi una freccia dal contatore di programma al registro istruzione, quello schema descrive una macchina che nella sezione 6 non esiste.

Le altre tre servono a eseguire, e sono la prova che lo schema e la traccia sono la stessa macchina. (G) → (D) è il passo 8, ACC ← MDR della LOAD. (G) → (C) è il passo 16, ACC ← ACC + MDR: l'ALU prende un operando dall'accumulatore e l'altro dal MDR, e per questo la freccia verso (C) esiste ma in un verso solo. (D) → (G) è il passo 23, MDR ← ACC della STORE. E qui si vede a occhio la cosa che la sezione 6 diceva a parole: fra (D) e (J) non c'è nessuna freccia diretta. L'accumulatore non tocca il bus dati; ci arriva passando per il MDR, che è esattamente il motivo per cui il passo 23 non si può saltare.

Perché (F) è il MAR e non il MDR: si affaccia su 12 linee unidirezionali, tante quante ne servono per 2¹² = 4.096 celle. Il registro dati dovrebbe essere largo 16 bit come la cella, e bidirezionale. Regola generale: il MAR è largo quanto il bus indirizzi, il MDR è largo quanto la cella.

4.096 celle x 16 b = 65.536 b
65.536 b : 8 = 8.192 B
8.192 B : 1.024 = 8 KiB
Che cosa manca: il bus di controllo (READ, WRITE, clock, interrupt) e tutto l'ingresso/uscita. Senza le linee di controllo i passi MDR <- M[MAR] e M[MAR] <- MDR dell'esercizio 7 avrebbero indirizzo e dato identici come struttura, e sarebbero indistinguibili.

Errore tipico. Rispondere «4.096 B = 4 KiB» leggendo solo il numero di celle e dimenticando che ogni cella vale 2 byte. E chiamare «memoria» i registri (A), (B), (D): lo sono in senso lato, ma non hanno un indirizzo — si raggiungono per nome dentro l'unità di controllo, non mettendo un numero sul bus.


Gli errori che ti costano il voto



Dieci errori, ciascuno smontato in quattro mosse: come lo pensano quasi tutti → perché sembra giusto → cosa succede davvero → come te ne accorgi.

1. Il bus fra le parti del modello di von Neumann. Quasi tutti: CPU, memoria, bus, ingresso, uscita. Sembra giusto: senza bus i pezzi non comunicano, e ogni schema di libro lo disegna. Davvero: il First Draft of a Report on the EDVAC (30 giugno 1945) non nomina mai il bus, e non usa nemmeno l'espressione «architettura di von Neumann». Il bus è un'astrazione successiva. Te ne accorgi: se nel tuo elenco ci sono sei voci, una è di troppo.

2. Contare R fra le cinque parti. Quasi tutti: l'outside recording medium R viene messo in fila con le altre. Sembra giusto: compare nello stesso paragrafo del rapporto, il § 2.6. Davvero: le parti numerate sono cinque — CA, CC, M, I, O — e von Neumann tiene R esplicitamente fuori dal dispositivo. Te ne accorgi: R è dove i dati stanno quando la macchina è spenta, non un organo della macchina.

3. «Von Neumann inventò il programma memorizzato». Quasi tutti: c'è il suo nome sull'architettura, quindi è sua l'idea. Sembra giusto: il documento porta solo la sua firma. Davvero: scrisse il testo, ma la Moore School ci lavorava da circa sette mesi prima che lui arrivasse, e la paternità delle idee è contesa; un tribunale federale si è espresso sui fatti nel 1973. Te ne accorgi: la formulazione prudente — «il rapporto che porta il suo nome descrive» — non è mai sbagliata.

4. Attribuire a Backus il memory wall. Quasi tutti: «collo di bottiglia» viene letto come «la RAM è lenta rispetto alla CPU». Sembra giusto: oggi è vero, ed è il problema che tutti conoscono. Davvero: Backus parlava nel 1977, quando il divario di velocità era modesto, e il suo era un limite concettuale e linguistico — il pensiero una parola per volta. Il memory wall è un problema degli anni Novanta in poi. Te ne accorgi: se la tua spiegazione del collo di bottiglia non nomina il modo di programmare, hai raccontato un'altra cosa.

5. MAR e MDR confusi, o MDR e MBR trattati come registri diversi. Quasi tutti: nei disegni stanno affiancati e finiscono nella stessa casella mentale. Sembra giusto: sono entrambi l'interfaccia verso la memoria. Davvero: il MAR dice dove, il MDR porta cosa; e MDR e MBR sono due nomi della stessa cosa, non due registri. Te ne accorgi: conta le linee. Il MAR è largo quanto il bus indirizzi, il MDR quanto la cella.

6. PC incrementato alla fine del ciclo. Quasi tutti: «finita l'istruzione, avanti alla prossima». Sembra giusto: è l'ordine cronologico intuitivo. Davvero: il PC si incrementa durante il fetch, al passo 3, subito dopo che l'indirizzo è stato usato. Solo così un'istruzione di salto, in fase di esecuzione, può sovrascrivere il valore appena incrementato — ed è quello che succede al passo 36 della traccia della sezione 6. Te ne accorgi: con l'incremento in fondo, ogni salto verrebbe annullato un attimo dopo. Se non sai spiegare come funziona un JMP, l'errore è questo.

7. «Un processore a 64 bit indirizza 2⁶⁴ byte». Quasi tutti: 64 bit di registro, 64 bit di indirizzo. Sembra giusto: è il numero stampato sulla scatola. Davvero: l'x86-64 impone la forma canonica e implementa 48 bit, cioè 256 TiB, che diventano 57 bit e 128 PiB con la paginazione a cinque livelli. Te ne accorgi: l'architettura dichiara 64 bit, l'hardware ne cabla molti meno.

8. La cache L1 presentata come unica. Quasi tutti: un blocco solo, etichettato L1. Sembra giusto: i livelli si contano 1, 2, 3. Davvero: nei processori reali la L1 è separata in L1i (istruzioni) e L1d (dati), come nelle tabelle della sezione 7, ed è esattamente il punto in cui l'architettura Harvard entra nei PC. Te ne accorgi: è la risposta alla domanda «e allora il mio PC è von Neumann o Harvard?».

9. Usare i valori SSD di Jeff Dean come attuali. Quasi tutti: si cita la tabella Latency Numbers Every Programmer Should Know e si scrive 150 µs per una lettura casuale da SSD. Sembra giusto: è la tabella più diffusa del mestiere. Davvero: i valori di memoria di massa risalgono a circa il 2012; gli NVMe attuali stanno a 20-70 µs, un ordine di grandezza meno. I valori per cache e RAM invece reggono. Te ne accorgi: se citi quella tabella, cita anche la data.

10. L'accumulatore dato per il registro operativo delle CPU moderne. Quasi tutti: «la CPU mette il risultato nell'accumulatore». Sembra giusto: è così in tutti i modelli didattici, compreso quello dell'esercizio 12. Davvero: i processori reali hanno banchi di registri generali — 16 su x86-64, 32 su AVR. Te ne accorgi: l'accumulatore singolo è il modello su cui si impara il ciclo macchina, non una descrizione dell'hardware di oggi. Dirlo per intero non costa niente e vale mezzo voto.

I tre errori di conto che tornano in ogni verifica

Bit e byte, fattore 8. 100 Mbit/s = 100.000.000 ÷ 8 = 12,5 MB/s. La «fibra da 1 Giga» dà 125 MB/s, quindi un gioco da 5 GB si scarica in 5.000 ÷ 125 = 40 secondi nel caso ideale, non in 5. E chi converte bit in byte dividendo per 1.024 invece che per 8 sbaglia di 128 volte.

Decimale contro binario. Lo scarto cresce con l'ordine di grandezza: +2,4% sui kilo, +4,9% sui mega, +7,4% sui giga, +10,0% sui tera. La regola operativa: massa e rete → decimale, RAM e indirizzamento → binario — ed è la regola che rimette a posto il conto della sezione 1, dove 500 GB di disco contro 8 GiB di RAM fanno 58 volte e non 62,5. Il caso diagnostico perfetto è il floppy da 1,44 MB, che vale 1.474.560 byte, cioè 1.440 KiB: non rispetta né l'una né l'altra convenzione, e serve proprio a farti capire che le convenzioni sono tre. La costruzione dei prefissi, se vuoi rivederla, sta nella guida sui sistemi di numerazione: binario, decimale, esadecimale; qui la applichiamo soltanto.

Cache della CPU contro cache del browser. 32 KiB contro 500 MB fanno 500.000.000 ÷ 32.768 = oltre 15.000 volte; 1 ns contro i 20-70 µs di un NVMe attuale fanno da 20.000 a 70.000 volte. Se usi il vecchio 150 µs della tabella di Jeff Dean ti viene 150.000, ma è lo stesso numero che l'errore 9 di questa sezione dichiara superato: o lo aggiorni, o dichiari che stai parlando di un disco meccanico. E soprattutto: la cache della CPU non è una cartella e non si svuota da un'interfaccia utente, è gestita in automatico dall'hardware. Istruzioni macchina per invalidarla esistono — su x86 sono CLFLUSH e WBINVD, su ARM le operazioni di data cache clean and invalidate — ma le usano il sistema operativo e i driver, per esempio quando una periferica scrive in memoria per conto suo, non tu da un menu.

E l'errore sui core, che produce una previsione falsificabile

I core non sono CPU indipendenti: condividono la L3, il controller di memoria e l'interconnessione. La legge di Amdahl mette i numeri:

Speedup = 1 : (1 - p + p/s)

p = 0,95   s -> infinito   ->   20 x      (non 100, non 1000)
p = 0,90   s = 8           ->   4,7 x
p = 0,90   s = 16          ->   6,4 x     ->  raddoppiare i core rende +36 %
E 8 core con SMT (simultaneous multithreading, l'esecuzione di due flussi di istruzioni sullo stesso core) si presentano al sistema operativo come 16 processori logici, ma le risorse di esecuzione restano una serie per core: i due thread se le prestano quando uno è in stallo. Un dual-core con SMT è meno potente di un vero quad-core, pur mostrando lo stesso numero nel Task Manager.

Perché queste convinzioni sono così dure a morire

Vale la pena dirtelo, perché non è colpa tua. In architettura, un modello mentale sbagliato non produce quasi mai un fallimento visibile. Se credi che il programma stia dentro la CPU non succede niente: il computer continua ad accendersi e a funzionare. Manca il ciclo di retroazione che nelle altre materie erode le idee sbagliate — è l'implicazione della tesi di Ben-Ari (Constructivism in Computer Science Education, SIGCSE 1998): lo studente principiante non possiede un modello efficace del computer, quindi il modello va insegnato, non presupposto. È l'opposto di quello che ti succede con l'algebra di Boole e le porte logiche, dove la tavola di verità è un tribunale d'appello che ti smentisce in quattro righe.

E poi c'è la seconda ragione: sono tutte la stessa convinzione. La memoria come contenitore, il software come sostanza immagazzinata dentro quel contenitore, la velocità come quantità che si accumula, il core come oggetto autonomo. Ecco perché correggerle una alla volta non funziona: cambi il contenitore e conservi lo schema.

La regola d'oro degli esercizi

Dichiara sempre la convenzione (1.000, 1.024, o l'ibrida del floppy se ti capita). Sempre l'ampiezza della cella (per il bus indirizzi). Sempre il CPI (per il tempo di esecuzione), e con lui che cosa conta come un ciclo. La maggior parte degli «errori dello studente» in questi ambiti sono, in realtà, esercizi mal posti — e scrivere la convenzione in cima al foglio è il modo più economico di non pagarli tu.


Domande frequenti



Il mio telefono dice 8 GB di RAM e 256 GB di ROM, ma il libro dice che la ROM è una memoria di sola lettura: chi sbaglia?
Sbaglia il volantino del negozio. I 256 GB del telefono sono memoria di massa riscrivibile: ci salvi le foto ogni giorno, quindi di "sola lettura" non hanno niente. La ROM vera è un'altra cosa: piccola, non volatile, non riscrivibile dall'utente, e soprattutto non ci si salvano file, perché non ospita un file system: contiene il codice di avvio della macchina. È per questo che alla domanda "la ROM è una memoria di massa?" si risponde no. Il marketing ha preso la sigla ROM e l'ha usata per dire "memoria interna", e ormai la trovi così su tutte le schede tecniche. Per la verifica tieni tre caselle separate: RAM = memoria centrale volatile e veloce; ROM = non volatile e di sola lettura; memoria interna del telefono = memoria di massa, come un SSD.

Ma allora il programma sta dentro la CPU?
No, e a dimostrarlo è il ciclo macchina stesso: il program counter contiene l'indirizzo della prossima istruzione, non l'istruzione. La CPU va a prendersela in memoria una alla volta, ed è la fase di fetch — che non esisterebbe affatto se il programma fosse già dentro. Attenzione però a non correggere troppo: un po' di memoria nella CPU c'è davvero, sono i registri e le cache. Solo che è pochissima. I registri general purpose di un x86-64 sono 16 da 64 bit, cioè 128 byte in tutto: rispetto a un programma da 1 MB, cioè 1.000.000 di byte, sono 128 diviso 1.000.000, lo 0,0128%. Tutto il resto è in RAM.

Quali sono le parti dell'architettura di von Neumann? Il mio libro ne elenca cinque, un altro sei.
Nel rapporto del 1945 le parti specifiche sono cinque, numerate una per una con un ordinale: CA (parte aritmetica centrale, quella che oggi chiami ALU) nel § 2.2, CC (controllo centrale) nel § 2.3, M (memoria) nei §§ 2.4-2.5, I (input) nel § 2.7, O (output) nel § 2.8. CA e CC insieme formano C nel § 2.6, ed è da lì che nasce la nozione moderna di CPU: la CPU non è una delle cinque parti, è la somma delle prime due. Chi ne conta sei di solito aggiunge R, il supporto esterno di registrazione: von Neumann lo introduce nello stesso § 2.6, ma lo tiene esplicitamente fuori dalle cinque parti, perché sta fuori dal dispositivo. E chi mette il bus nell'elenco sbaglia proprio storia: nel rapporto la parola bus non compare mai, è un'astrazione arrivata dopo.

Il mio PC è di von Neumann o è Harvard?
Tutti e due, a due livelli diversi. Von Neumann vuol dire una memoria sola per istruzioni e dati, con un bus solo; Harvard vuol dire due memorie fisicamente separate, due bus, e anche larghezze libere su ciascun lato: in un AVR a 8 bit i dati stanno su 8 bit e le istruzioni su 16, e l'istruzione arriva in un colpo solo. In una memoria unica quell'istruzione non è impossibile, si spalma su più celle: costa solo più accessi. Il tuo PC ha un unico spazio di indirizzamento, quindi verso il programma è von Neumann; ma dentro il processore la cache di primo livello è divisa in L1i (istruzioni) e L1d (dati), e quello è Harvard. Si dice Harvard modificata. Harvard pura la trovi nei microcontrollori, non nei PC.

Il program counter si incrementa all'inizio o alla fine dell'istruzione?
Durante il fetch, subito dopo che il suo valore è stato usato per interrogare la memoria — non alla fine del ciclo. Sembra un dettaglio e invece decide tutto: se l'istruzione in esecuzione è un salto, quel salto scrive un nuovo indirizzo nel PC, e un incremento successivo cancellerebbe la destinazione. Il fetch è di quattro micro-operazioni, non cinque: MAR ← PC, MDR ← memoria, PC ← PC + 1, IR ← MDR, e solo dopo decodifica ed esecuzione. Il segnale READ sul bus di controllo non è un passo in più, accade insieme alla lettura. Il "+1" vale nel modello didattico con istruzioni tutte della stessa ampiezza; nelle macchine vere si somma la lunghezza dell'istruzione appena prelevata.

Che differenza c'è fra MAR e MDR? E l'MBR cos'è ancora?
MAR e MDR non sono sinonimi: sono i due lati della stessa conversazione con la memoria. Il MAR (memory address register) dice dove, ed è largo quanto il bus indirizzi; il MDR (memory data register) porta cosa, ed è largo quanto la cella. Se in uno schema li vedi affiancati con larghezze diverse, per esempio 12 linee e 16, il numero di linee ti dice al volo chi è chi. L'MBR (memory buffer register) invece è soltanto un altro nome dell'MDR: stessa identica cosa, due sigle, e i libri usano l'una o l'altra senza avvisarti.

Un processore da 3 GHz esegue tre miliardi di istruzioni al secondo?
No: il clock conta i cicli, non le istruzioni. Il tempo di un programma è un prodotto di tre fattori — numero di istruzioni × CPI × durata del ciclo — e la frequenza tocca solo l'ultimo. Il CPI, cioè i cicli per istruzione, dipende anche dal miscuglio di istruzioni: nella macchina didattica di questa guida una LOAD costa 8 cicli e un salto ne costa 6. Con CPI 4, un processore a 3 GHz esegue 750 milioni di istruzioni al secondo, non tre miliardi. E il confronto fra due macchine si ribalta facilmente: A a 3,0 GHz con CPI 5 fa 600 MIPS, B a 2,4 GHz con CPI 3 ne fa 800. Vince B, quello con il clock più basso. Il caso storico è del 18 luglio 2001, quando Apple mostrò dal palco del Macworld Expo un G4 a 867 MHz completare in 45 secondi un lavoro che a un Pentium 4 da 1,7 GHz ne costava 82.

Un processore a 64 bit può indirizzare 2⁶⁴ byte di memoria?
No, e non lo fa nessun processore esistente. La regola vera è che con n linee di bus indirizzi si indirizzano 2ⁿ celle, e le linee davvero cablate sono molte meno di 64: l'x86-64 ne implementa 48, cioè 281.474.976.710.656 indirizzi, 256 TiB (57 bit e 128 PiB con la paginazione a cinque livelli). Attenzione al passaggio da celle a byte: 2ⁿ conta le celle, e coincide con i byte solo se la cella è da un byte. Il conto 2ⁿ però è esatto e spiega la storia: l'Intel 8086 del 1978 aveva 20 linee di indirizzo e celle da un byte, quindi 2²⁰ = 1.048.576 byte = 1 MiB. Il famigerato tetto di memoria del DOS era soltanto la larghezza di un bus.

La memoria cache è quella che si svuota dal browser?
Il principio è lo stesso — tenere una copia vicina per non andare a prendere le cose lontano — ma sono due oggetti separati da cinque ordini di grandezza. La cache della CPU è SRAM sul chip del processore: 32-64 KiB di L1 per core, letti in circa 1 nanosecondo, cioè 4-5 cicli di clock. La cache del browser sono file su disco: centinaia di MB, letti in decine di microsecondi. E soprattutto la cache della CPU non è una cartella e non si svuota da un'interfaccia utente: non ha un percorso, non ha un pulsante, è gestita in automatico dall'hardware. Istruzioni macchina per invalidarla esistono, su x86 sono CLFLUSH e WBINVD, ma le usano il sistema operativo e i driver per la coerenza con le periferiche, non tu.

Devo sapere il binario e le porte logiche prima di studiare l'architettura?
Sì, e questa guida li dà per fatti. Il binario e l'esadecimale ti servono per leggere gli indirizzi e il contenuto delle celle, e stanno nella guida sui sistemi di numerazione. Le porte AND, OR e NOT sono il materiale con cui è costruita l'ALU, e stanno nella guida sull'algebra di Boole — che però, va detto, non è prescritta da nessuno dei documenti ministeriali citati in questa guida: è un prerequisito didattico reale, non un obbligo di programma. I diagrammi di flusso stanno nella guida sugli algoritmi: qui vedi la macchina che quei diagrammi li esegue per davvero, e il rombo di decisione diventa un salto condizionato che scrive nel program counter. Una sorpresa: nelle Linee guida dei tecnici, fra le Conoscenze di Tecnologie informatiche, la codifica dei dati è elencata prima dell'architettura, mentre il concetto di algoritmo viene dopo.

Fonti: J. von Neumann, First Draft of a Report on the EDVAC, 30 giugno 1945, edizione critica a cura di M. D. Godfrey, IEEE Annals of the History of Computing, vol. 15, n. 4, 1993, pp. 27-75 · J. Backus, Can Programming Be Liberated from the von Neumann Style?, Communications of the ACM, vol. 21, n. 8, agosto 1978, pp. 613-641 · W. S. McCulloch, W. Pitts, A logical calculus of the ideas immanent in nervous activity, Bulletin of Mathematical Biophysics, vol. 5, 1943, pp. 115-133 · Indicazioni nazionali per il curricolo della scuola dell'infanzia e del primo ciclo, Allegato al DM 9 dicembre 2025 n. 221 · Indicazioni nazionali per i licei, Allegato F al DM 7 ottobre 2010 n. 211 · Direttiva MIUR n. 57 del 15 luglio 2010, Allegato A.2 · brevetto USA 3.120.606 e sentenza Honeywell v. Sperry Rand, 19 ottobre 1973 · datasheet Microchip/Atmel ATmega32 e ATmega128

0 commenti

Nessun commento. Se qualcosa non ti torna o vuoi aggiungere un pezzo, scrivi tu il primo.

Vuoi commentare? Serve un account: si fa in trenta secondi e non chiediamo conferme via email.

Crea un account Ho già un account