
La traduzione dell'articolo è preparata per gli studenti del corso .
In precedenza ho parlato di come controllare e abilitare l'utilizzo di HugePages in Linux.
Questo articolo sarà utile solo se hai realmente un contesto in cui usare HugePages. Ho incontrato molte persone che si illudono riguardo alla prospettiva che HugePages aumenti magicamente le prestazioni. Tuttavia, l'uso di HugePages è un argomento complesso e, se usato in modo errato, può ridurre le prestazioni.
Parte 1: controlliamo che HugePages sia abilitato in Linux (originale )
Problema:
Devi verificare se HugePages è abilitato nel tuo sistema.
Soluzione:
È piuttosto semplice:
cat /sys/kernel/mm/transparent_hugepage/enabledRiceverai qualcosa del genere:
always [madvise] neverVedrai un elenco delle opzioni disponibili (always, madvise, never), con l'opzione attualmente attiva racchiusa tra parentesi (per impostazione predefinita madvise).
madvise significa che transparent hugepages sono abilitati solo per le aree di memoria che richiedono esplicitamente HugePages tramite .
always significa che transparent hugepages è sempre abilitato e per tutti i processi. Questo di solito aumenta le prestazioni, ma se hai un caso d'uso in cui molti processi utilizzano una piccola quantità di memoria, il carico complessivo sulla memoria può aumentare drasticamente.
never significa che transparent hugepages non verrà abilitato nemmeno se richiesto tramite madvise. Per ulteriori informazioni, consulta il kernel Linux.
Come cambiare il valore predefinito
Opzione 1: Modificare direttamente sysfs (dopo il riavvio, il parametro tornerà al valore predefinito):
echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabledOpzione 2: Cambiare il valore predefinito di sistema ricompilando il kernel con una configurazione modificata (questa opzione è raccomandata solo se utilizzi un kernel personalizzato):
- Per impostare always come predefinito, usa:
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y # Commenta CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y - Per impostare madvise come predefinito, usa:
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y # Commenta CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
Parte 2: Vantaggi e svantaggi di HugePages
Cercheremo di spiegare in modo selettivo i vantaggi, gli svantaggi e gli errori possibili nell'uso delle Hugepages. Poiché un articolo tecnicamente complesso e pedante potrebbe risultare difficile da comprendere per chi è fuorviato nel considerare le Hugepages una panacea, sacrificherò la precisione a favore della semplicità. È importante tenere presente che molti argomenti sono realmente complessi e quindi fortemente semplificati.
Si noti che stiamo parlando di sistemi x86 a 64 bit che operano su Linux, e che presumo semplicemente che il sistema supporti le transparent hugepages (poiché non è un difetto il fatto che le hugepages non vengano sostituite), come accade praticamente in qualsiasi ambiente Linux moderno.
Nei collegamenti qui sotto aggiungerò ulteriori descrizioni tecniche.
Memoria virtuale
Se sei un programmatore C++, sai che gli oggetti in memoria hanno indirizzi specifici (valori dei puntatori).
Tuttavia, questi indirizzi non riflettono necessariamente gli indirizzi fisici in memoria (indirizzi nella RAM). Rappresentano indirizzi in memoria virtuale. Il processore ha un modulo speciale MMU (unità di gestione della memoria) che aiuta il kernel a mappare la memoria virtuale con la posizione fisica.
Questo approccio ha numerosi vantaggi, ma i più rilevanti sono:
- Prestazioni (per vari motivi);
- Isolamento delle applicazioni, cioè nessuna delle applicazioni può leggere dalla memoria di un'altra applicazione.
Che cos'è una pagina?
La memoria virtuale è suddivisa in pagine. Ogni singola pagina punta a una certa memoria fisica, potendo riferirsi a un'area nella RAM o a un indirizzo assegnato a un dispositivo fisico, ad esempio una scheda grafica.
La maggior parte delle pagine con cui hai a che fare punta o alla RAM o è sostituita (swap), ovvero è memorizzata su disco rigido o SSD. Il kernel gestisce la posizione fisica di ogni pagina. Quando viene effettuato l'accesso a una pagina sostituita, il kernel interrompe il thread che tenta di accedere alla memoria, legge la pagina dal disco rigido/SSD nella RAM e continua l'esecuzione del thread.
Questo processo è trasparente per il thread, cioè non legge direttamente dal disco rigido/SSD. La dimensione delle pagine normali è di 4096 byte. La dimensione delle Hugepages è di 2 megabyte.
Buffer di traduzione associativa (TLB)
Quando un programma accede a una certa pagina di memoria, la CPU deve sapere da quale pagina fisica leggere i dati (cioè avere una mappa degli indirizzi virtuali).
Nel kernel c'è una struttura dati (tabella delle pagine) che contiene tutte le informazioni sulle pagine utilizzate. Con questa struttura dati è possibile mappare un indirizzo virtuale a un indirizzo fisico.
Tuttavia, la tabella delle pagine è piuttosto complessa e funziona lentamente, quindi non possiamo analizzare ogni volta l'intera struttura dati quando un processo accede alla memoria.
Fortunatamente, nel nostro processore c'è un TLB che memorizza nella cache il rapporto tra indirizzi virtuali e fisici. Ciò significa che, sebbene dobbiamo analizzare la tabella delle pagine al primo tentativo di accesso, tutti gli accessi successivi alla pagina possono essere gestiti dal TLB, garantendo così un funzionamento rapido.
Poiché è implementato come un dispositivo fisico (il che lo rende innanzitutto veloce), la sua capacità è limitata. Pertanto, se si desidera accedere a un numero maggiore di pagine, il TLB non sarà in grado di memorizzare la corrispondenza per tutte, e di conseguenza il programma funzionerà molto più lentamente.
Gli Hugepages vengono in aiuto
Quindi, cosa possiamo fare per evitare che il TLB si riempia? (Assumiamo che il programma abbia ancora bisogno della stessa quantità di memoria).
Ed è qui che entrano in gioco gli Hugepages. Invece di 4096 byte, che richiedono solo una voce nel TLB, ora una voce nel TLB può puntare a colossali 2 megabyte. Supponiamo che il TLB abbia 512 voci, qui senza Hugepages possiamo mappare:
4096 b⋅512=2 MBMentre con essi possiamo mappare:
2 MB⋅512=1 GBEcco perché gli Hugepages sono fantastici. Possono aumentare le prestazioni senza significativi sforzi. Ma ci sono alcune avvertenze importanti.
Sostituzione degli Hugepages
Il kernel tiene automaticamente traccia della frequenza di utilizzo di ogni pagina di memoria. Se la memoria fisica (RAM) non è sufficiente, il kernel sposterà le pagine meno importanti (utilizzate meno di frequente) sul disco rigido per liberare parte della RAM per le pagine più importanti.
In linea di principio, lo stesso vale per gli Hugepages. Tuttavia, il kernel può scambiare solo intere pagine e non singoli byte.
Supponiamo di avere questo programma:
char* mymemory = malloc(2*1024*1024); // Prendiamolo come una Hugepage!
// Riempiremo mymemory con alcuni dati
// Faremo molte altre cose,
// che porteranno a un cambio della pagina mymemory
// ...
// Chiediamo accesso solo al primo byte
putchar(mymemory[0]); In questo caso, il kernel dovrà leggere ben 2 megabyte di informazioni dal disco rigido/SSD solo per farti leggere un byte. Per quanto riguarda le pagine normali, dal disco rigido/SSD deve essere letta solo una quantità di 4096 byte.
Pertanto, se una hugepage viene cambiata, la sua lettura avviene più velocemente, solo se hai bisogno di accedere all'intera pagina. Ciò significa che se stai cercando di accedere in modo casuale a diverse parti della memoria e leggi solo un paio di kilobyte, dovresti usare pagine normali e non pensarci ulteriormente.
D'altro canto, se hai bisogno di accedere a una grande parte della memoria in modo sequenziale, le hugepages aumenteranno le tue prestazioni. Tuttavia, dovresti verificare da solo (e non con un software astratto) e osservare cosa funziona più velocemente.
Allocazione in memoria
Se stai scrivendo in C, sai che puoi richiedere quantità di memoria di qualunque dimensione (o quasi) dalla heap usando malloc(). Supponiamo che tu abbia bisogno di 30 byte di memoria:
char* mymemory = malloc(30);Per un programmatore, potrebbe sembrare che tu stia “richiedendo” 30 byte di memoria dal sistema operativo e restituendo un puntatore a una certa memoria virtuale. Ma in realtà malloc () è semplicemente una funzione C che richiama internamente le funzioni per richiedere o liberare memoria dal sistema operativo.
Tuttavia, richiedere sempre più memoria per ogni allocazione non è efficiente; è molto probabile che un segmento di memoria sia già stato liberato (free()), e possiamo riutilizzarlo. malloc() implementa algoritmi piuttosto complessi per il riutilizzo della memoria liberata.
In questo modo, tutto avviene senza che tu te ne accorga, quindi perché dovrebbe interessarti? Perché la chiamata free() non significa che .
Esiste un concetto chiamato frammentazione della memoria. Nei casi estremi, ci sono segmenti dell'heap in cui vengono utilizzati solo pochi byte, mentre tutto ciò che si trova tra di essi è stato liberato. (free()).
Si noti che la frammentazione della memoria è un argomento incredibilmente complesso e anche piccole modifiche nel programma possono influenzarla notevolmente. Nella maggior parte dei casi, i programmi non causano una frammentazione significativa della memoria, ma dovresti tenere presente che se si verifica un problema di frammentazione in una certa area dell'heap, gli hugepages possono solo aggravare la situazione.
Applicazione selettiva degli hugepages
Dopo aver letto l'articolo, hai identificato quali parti del tuo programma possono beneficiare dell'applicazione degli hugepages e quali no. Dovresti quindi abilitare gli hugepages?
Fortunatamente, puoi utilizzare madvise(), per abilitare l'huepaging solo per quelle aree di memoria dove sarà utile.
Per iniziare, verifica che gli hugepages funzionino in modalità madvise(), usando all'inizio dell'articolo.
Quindi, usa madvise(), per indicare al kernel dove utilizzare gli hugepages.
#include <sys/mman.h>
// Аллоцируйте большое количество памяти, которую будете использовать
size_t size = 256*1024*1024;
char* mymemory = malloc(size);
// Просто включите hugepages…
madvise(mymemory, size, MADV_HUGEPAGE);
// … и задайте следующее
madvise(mymemory, size, MADV_HUGEPAGE | MADV_SEQUENTIAL)Si noti che questo metodo è semplicemente un consiglio al kernel per la gestione della memoria. Non significa che il kernel utilizzerà automaticamente gli hugepages per la memoria designata.
Fai riferimento alla documentazione , per saperne di più sulla gestione della memoria e madvise(), questo argomento ha una curva di apprendimento incredibilmente ripida. Pertanto, se intendi approfondirlo veramente, preparati a leggere e testare per alcune settimane prima di aspettarti qualsiasi risultato positivo.
Cosa leggere?
Hai domande? Scrivi nei commenti!
Fonte: habr.com
