Esercizio - Località dei riferimenti, scorrere una matrice per righe o per colonne (laboratorio 7)
In questa pagina 4
Testo (laboratorio 7 del corso di Sistemi Operativi, Ing. Informatica UniPD).
- Sistema con pagine da 1024 byte e un solo frame,
byte data[20][1024](20 pagine). Quanti page fault fanno i due programmi che azzerano la matrice, uno confor i … for j …e l'altro confor j … for i …? - Task 1. I file
pagetest1.c(scorre per righe) epagetest2.c(scorre per colonne) riempiono una matrice di interi di 1024 righe per 1024 colonne conrand(). Si compilano, si misurano con./memtest ./pagetest1(script che esegueperf stat -e cache-misses,dTLB-load-misses,dTLB-store-misses,page-faults) e si confrontano cache miss, TLB miss e page fault. Sono diversi? - Task 2. Ripetere la misura per
ROWS = 1024eCOLS= 256, 512, 1024, 2048, 4096, con 10 ripetizioni per combinazione, e calcolare la media dei cache miss.
Parte 1: i fault con un solo frame
data[20][1024] di byte, con righe da 1024 byte, occupa 20 pagine da 1024 byte: ogni riga è esattamente una pagina. Il C salva le matrici per righe: data[0][0], data[0][1], …, data[0][1023], data[1][0], ….
Programma 1 (for i … for j …): scorre in ordine di indirizzo. Tutta la riga sta nella pagina : si riferisce la pagina 0 per 1024 volte, poi la 1 per 1024 volte, e così via. Con un solo frame: un fault a ogni cambio di pagina,
Programma 2 (for j … for i …): per ogni colonna si visitano le 20 righe, cioè le pagine , e poi si torna alla pagina 0 per la colonna successiva. Con un solo frame ogni accesso cambia pagina: un fault a ogni scrittura,
Lo stesso lavoro, con una differenza di 1024 volte: dipende solo dall'ordine dei cicli (dalla località dei riferimenti).
Parte 2: i due programmi del laboratorio
Con la RAM reale e senza vincolo di un frame, il sistema ha abbastanza memoria: ogni pagina viene caricata (con un page fault) la prima volta che la si tocca e poi resta. La matrice di interi occupa , cioè pagine da 4 KB. Quindi:
| contatore | scorrimento per righe (pagetest1) |
per colonne (pagetest2) |
|---|---|---|
| page fault | circa 1024 (una per pagina; con RAM sufficiente è uguale nei due casi) | circa 1024 (uguale) |
| cache miss | pochi: gli accessi sono consecutivi, una riga di cache da 64 B contiene 16 interi, quindi circa miss di primo accesso | molti di più: ogni accesso salta di byte, in una riga di cache diversa, e le righe di cache usate dagli altri accessi vengono sostituite prima di essere riutilizzate |
| dTLB miss | circa uno per pagina (1024) | molti di più: ogni accesso cade in una pagina diversa e le 1024 pagine non stanno nelle poche decine o centinaia di righe del TLB |
Il commento della slide: con il sistema che assegna abbastanza frame, i TLB e le cache sono "precaricati", quindi il numero di page fault non cambia, cambiano il numero di TLB miss e di cache miss. In generale, il tempo di esecuzione di pagetest2 è molto maggiore di quello di pagetest1.
Il programma completo (in versione con la matrice come variabile statica, per evitare il limite dello stack da 8 MB; in pagetest1.c del corso la matrice è locale: per questo serve ulimit -s unlimited):
/* localita.c - scorrere una matrice per righe o per colonne.
* Compilare: gcc -O0 -Wall -Wextra -o localita localita.c
* Usare: ./localita righe oppure ./localita colonne
* Misurare: perf stat -e cache-misses,dTLB-load-misses,dTLB-store-misses,page-faults ./localita righe
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define RIGHE 1024
#define COLONNE 1024
static int dati[RIGHE][COLONNE]; /* 4 MB, nei dati statici: niente problema con lo stack da 8 MB */
int main(int argc, char **argv) {
if (argc != 2) { fprintf(stderr, "uso: %s righe|colonne\n", argv[0]); return 1; }
srand(1);
if (strcmp(argv[1], "righe") == 0) {
for (int i = 0; i < RIGHE; i++) /* indirizzi consecutivi: 1 pagina ogni 1024 scritture */
for (int j = 0; j < COLONNE; j++)
dati[i][j] = rand();
} else {
for (int j = 0; j < COLONNE; j++) /* salto di 4096 byte a ogni scrittura */
for (int i = 0; i < RIGHE; i++)
dati[i][j] = rand();
}
printf("%d\n", dati[RIGHE / 2][COLONNE / 2]); /* evita che il compilatore elimini il ciclo */
return 0;
}(Compilato qui con zig cc per Linux, senza avvisi; non eseguito: le misure con perf richiedono una macchina Linux, per cui i valori della tabella sono stime ragionate, non misure.)
Parte 3: variare le colonne
Con ROWS = 1024 e COLS la matrice è di byte: 1, 2, 4, 8, 16 MB, cioè 256, 512, 1024, 2048, 4096 pagine. I page fault crescono in modo proporzionale (uno per pagina). I cache miss:
- scorrendo per righe crescono linearmente con (un miss ogni 16 interi consecutivi): è il minimo possibile;
- scorrendo per colonne, il salto fra due accessi è di byte: quando le righe di cache toccate in una colonna (una per riga della matrice, 1024 righe di cache KB) non stanno nella cache, ogni accesso è un miss, e il numero cresce come , fino a 16 volte più dei cache miss per righe.
Procedura per la parte di misura: compilare ogni coppia (), eseguire 10 volte perf stat, calcolare la media dei cache-misses e fare il grafico (COLS in ascissa, media in ordinata, una curva per ciascun programma). Si può automatizzare con uno script di shell che ripete l'esecuzione e usa perf stat -x, per estrarre il valore. Il grafico atteso: la curva "righe" cresce con pendenza piccola, quella "colonne" con pendenza molto maggiore.
Errori tipici
- Dire che i page fault cambiano molto fra i due programmi quando c'è RAM sufficiente: cambiano TLB e cache.
- Credere che il C salvi le matrici per colonne (è per righe, row-major).
- Confondere pagina del sistema operativo (4096 byte) con riga di cache (64 byte).
- Calcolare i fault con un frame senza accorgersi che ogni riga è una pagina (1024 byte).
- Dimenticare
ulimit -s unlimitedquando la matrice da 4 MB è una variabile locale.
Versione ripasso
Un frame, byte data[20][1024], pagine da 1024 byte. Per righe (i esterno): 20 fault. Per colonne (j esterno): fault. Il C salva per righe (row-major): una riga è una pagina.
Matrice di interi (4 MB pagine da 4 KB), RAM sufficiente. Page fault: circa 1024 in entrambi i programmi. Cache miss: per righe circa (riga di cache da 64 B); per colonne molti di più (salto di 4096 B). TLB miss: per righe circa 1024; per colonne molti di più. Il numero di page fault non cambia, cambiano TLB e cache.
Varianti di COLS (256 ... 4096): pagine (con ROWS ); cache miss per righe , per colonne fino a 16 volte tanto. Media su 10 esecuzioni con perf stat; grafico COLS contro cache miss.
Numeri da ricordare. Matrice di int: 4 MB pagine da 4 KB; 1 048 576 scritture; riga di cache da 64 B interi circa 65 536 miss per righe; per colonne salto di 4096 B a ogni scrittura. Misura: ./memtest ./pagetest1 (ulimit -s unlimited, perf stat -e cache-misses,dTLB-load-misses,dTLB-store-misses,page-faults). Con COLS (ROWS ): 1, 2, 4, 8, 16 MB COLS pagine; media di 10 esecuzioni; grafico COLS contro cache miss.
Errori tipici: page fault diversi con RAM sufficiente; matrici per colonne; pagina (4096) e riga di cache (64) scambiate; ulimit -s dimenticato.