Vai al contenuto
Offerte

Memoria, il vero collo di bottiglia dei computer moderni

In questo articolo Indice dei contenuti 2 sezioni

Le prestazioni della memoria sono diventate il vero collo di bottiglia dell’informatica moderna, molto più di quanto lo siano i gigahertz o il numero di core stampati sulla scheda tecnica. Per decenni la potenza di un computer si riassumeva in poche cifre facili da confrontare: frequenza di clock per le CPU, teraFLOPS per le GPU, megahertz per le RAM, velocità di trasferimento sequenziale per gli archivi. Quei numeri contano ancora, per carità. Solo che i processori sono diventati così bravi a fare calcoli che il problema si è spostato altrove, cioè nel portare i dati dove servono, nel momento esatto in cui servono.

Un core moderno esegue più istruzioni per ciclo di clock. Una GPU di fascia alta contiene migliaia di unità aritmetiche che lavorano in parallelo. Gli acceleratori per intelligenza artificiale spingono la moltiplicazione tra matrici fino alla scala dei petaFLOPS. Ma nessuna di queste unità produce qualcosa di utile senza operandi, istruzioni, texture, geometrie, pesi dei modelli o risultati intermedi. È qui che nasce lo squilibrio: la capacità di calcolo è cresciuta enormemente, mentre spostare i dati resta costoso in termini di latenza, banda, energia e spazio fisico sul chip. Molti carichi di lavoro passano una quantità sorprendente di tempo semplicemente a tenere alimentate le unità di esecuzione.

Attenzione, questo non significa che ogni applicazione sia limitata dalla memoria. Le prestazioni possono restare vincolate dalla potenza di calcolo pura, dalle dipendenze tra istruzioni, dalla previsione dei salti, dalla sincronizzazione, dal software, dalla rete o dallo storage. Quello che è cambiato è che senza capire il comportamento della memoria non si capisce più nulla delle prestazioni complessive. E il concetto va ben oltre frequenza e capacità: entrano in gioco latenza, banda, località, dimensione della cache, parallelismo, schemi di accesso, prefetching, topologia delle interconnessioni e persino la posizione fisica dei dati.

Memoria: accesso sequenziale e accesso casuale, due mondi separati

Leggere un file da 1 GB dall’inizio alla fine è la situazione ideale. La posizione del dato successivo è perfettamente prevedibile e il sistema, una volta capito lo schema, inizia a caricare in anticipo. Questo è l’accesso sequenziale, e le macchine moderne lo gestiscono benissimo. Le CPU hanno prefetcher hardware progettati apposta per riconoscere flussi prevedibili, i controller DRAM tengono più transazioni in volo e riordinano le richieste, i controller SSD distribuiscono gli accessi tra molti die NAND e più canali, le GPU fondono le richieste di thread vicini in transazioni più grandi.

L’accesso casuale è tutta un’altra storia. Basta pensare a una lista concatenata dove ogni elemento contiene l’indirizzo del successivo: il processore non sa dove andare finché l’accesso corrente non è concluso. Addio prefetching, addio sovrapposizione delle operazioni. Due carichi di lavoro possono muovere la stessa identica quantità di dati e girare a velocità drammaticamente diverse. Il fenomeno si chiama pointer chasing e costringe il chip a pagare per intero la latenza reale, senza poterla nascondere. Un database che fa piccole ricerche imprevedibili vive in un universo diverso rispetto alla copia di un file di grandi dimensioni, così come un SSD che legge centinaia di kilobyte in fila lavora in modo completamente diverso rispetto a uno che recupera blocchi sparsi da 4 KB.

Gli sviluppatori provano a trasformare il casuale in sequenziale, questo sì. I database riorganizzano i dati, i motori di gioco raggruppano il lavoro, i compilatori cambiano la disposizione delle strutture. Ma se l’indirizzo successivo dipende dal risultato dell’operazione corrente, la dipendenza è reale e non si aggira. Un raggio che rimbalza verso una zona imprevedibile di una scena tridimensionale non si può prevedere.

Perché esistono più livelli di cache

Il software, per fortuna, raramente accede a dati completamente arbitrari. Esistono la località spaziale, cioè la tendenza a usare dati vicini a quelli appena letti, e la località temporale, cioè la tendenza a riusare presto gli stessi dati. Un ciclo che elabora un array ha un’ottima località spaziale. Su queste proprietà si reggono le cache, che trasferiscono blocchi di dimensione fissa chiamati linee di cache, tipicamente da 64 byte sulle CPU x86 mainstream. La documentazione di ottimizzazione di AMD ragiona proprio attorno a quel valore.

Conta poi il working set, cioè la porzione di memoria realmente attiva in una certa fase. Un programma può allocare decine di gigabyte e lavorare su una frazione minima. Se quella frazione entra nella cache veloce, le prestazioni volano. Se la supera di poco, moltissimi accessi scivolano su un livello più lento e il calo può essere improvviso. Ecco perché passare da 32 MB a 96 MB di cache a volte cambia tutto e altre volte quasi niente: serve se un working set da 60 MB resta sul chip, serve poco se il carico attraversa gigabyte di dati che non rileggerà mai.

Perché allora non costruire una cache gigantesca e velocissima? Perché le proprietà desiderate confliggono tra loro. La SRAM dei livelli più vicini al core offre latenza bassissima e banda enorme, ma occupa molto silicio e più cresce più richiede cablaggi, percorsi fisici lunghi, logica di ricerca ed energia. La L1 sta attaccata al core e risponde in un lampo, la L2 offre più spazio con qualche ciclo in più, la L3 è ancora più capiente e spesso condivisa tra i core, ma più lenta. Sbagliato ogni livello, la richiesta finisce nella DRAM. Intel ha descritto l’organizzazione della cache come un equilibrio continuo tra dimensione, associatività, latenza, banda, struttura privata o condivisa, scalabilità ed efficienza energetica.

Condividi:
45 Condivisioni