Come causare la compattazione della memoria frammentata del kernel


8

Sto correndo Fedora 26.

Questo è per un compito molto strano dato dal mio professore di algoritmi. Il compito dice:

Frammentazione della memoria in C:
progettare, implementare ed eseguire un programma C che procede come segue: alloca memoria per una sequenza di 3mmatrici di 800.000 elementi ciascuna; quindi dealloca esplicitamente tutti gli array con numero pari e alloca una sequenza di marray di dimensioni di 900.000 elementi ciascuno. Misura il tempo necessario al tuo programma per l'allocazione della prima sequenza e per la seconda sequenza. Scegli mdi esaurire quasi tutta la memoria principale disponibile per il tuo programma. "

L'obiettivo generale di questo è frammentare la memoria, quindi richiedere un po 'più di quello disponibile come blocco contiguo, costringendo il sistema operativo a compattare o deframmentare la memoria.

In classe ho chiesto come dovremmo procedere dal momento che la memoria è visualizzata e in realtà non contigua, alla quale ha risposto: "Beh, dovrai disattivare [memoria virtuale]". Alcuni altri studenti hanno chiesto in classe come dovremmo sapere quando abbiamo raggiunto questa "raccolta dei rifiuti", e ha detto che: "I tempi per la seconda assegnazione dovrebbero essere maggiori del primo a causa del tempo impiegato dalla raccolta dei rifiuti"

Dopo aver cercato un po 'in giro, la cosa più vicina che ho trovato a disabilitare la memoria virtuale è stata disabilitare la memoria di scambio swapoff -a. Ho disabilitato il mio ambiente desktop e compilato ed eseguito il mio programma dal terminale nativo (per evitare possibili interferenze da altri processi, in particolare uno pesante come l'ambiente desktop). Ho fatto questo e ho eseguito il mio programma con un aumento mfino a quando non ho raggiunto un punto in cui i tempi per la seconda assegnazione erano maggiori del primo.

Ho eseguito il programma con crescente me alla fine ho trovato un punto in cui il tempo per la seconda assegnazione era più del tempo per la prima assegnazione. Lungo la strada, tuttavia, ho raggiunto un punto in cui il processo è stato interrotto prima della seconda assegnazione. Ho controllato dmesge ho visto che è stato ucciso daoom -killer. Ho trovato e letto diversi articoli su oom-killer e ho scoperto che era possibile disabilitare l'allocazione di memoria da parte del kernel.

Ho fatto questo ed eseguito di nuovo il mio programma, solo che questa volta non sono stato in grado di trovare un m tale che il tempo del secondo fosse superiore al primo. Alla fine con m sempre più grandi (anche se molto più piccoli di quando era abilitata la sovrallocazione) malloc fallirebbe e il mio programma sarebbe terminato.

Ho tre domande, la prima delle quali non è poi così importante:

  1. Garbage Collection è il termine corretto per questo? Il mio professore è molto irremovibile nel dire che si tratta della raccolta dei rifiuti, ma io supponevo che la raccolta dei rifiuti fosse fatta dai linguaggi di programmazione e che questo sarebbe stato considerato più deframmentante.

  2. La compattazione è come vuole su un sistema Linux?

  3. Perché sono riuscito a raggiungere un punto in cui il tempo per la seconda allocazione era maggiore del primo quando disabilitavo lo scambio ma avevo ancora abilitato l'allocazione eccessiva di memoria? La compattazione ha effettivamente avuto luogo? In caso affermativo, perché non sono riuscito a raggiungere un punto in cui è avvenuta la compattazione dopo aver disabilitato la sovrallocazione della memoria?


Non è possibile "disattivare" la memoria virtuale. Inoltre, tieni presente che in Linux puoi allocare logicamente più memoria di quella che effettivamente hai - il kernel non allocerà effettivamente le pagine finché non le scrivi.
Andy Dalton,

3
Mi aspettavo ancora un altro Scrivi i miei compiti! domanda. Ma piuttosto questo è un mio compito sembra male specificato, sconsiderato e impossibile. È? domanda. Parte di questo è il territorio di Stack Overflow e troverai molte domande e risposte lungo le linee di (solo per cogliere un esempio a caso) stackoverflow.com/questions/4039274 .
JdeBP,

Risposte:


5

Complimenti per la tua ricerca finora, questa è davvero una serie interessante di domande.

C'è un aspetto importante da considerare qui in generale: l'allocazione della memoria è in parte la responsabilità del sistema operativo e in parte la responsabilità di ogni processo in esecuzione (ignorando i vecchi sistemi senza protezione della memoria e spazi di indirizzi virtuali). Il sistema operativo si occupa di fornire a ciascun processo il proprio spazio di indirizzi e di allocare memoria fisica ai processi quando necessario. Ogni processo si occupa di ritagliare il proprio spazio di indirizzi (in una certa misura) e garantire che venga utilizzato in modo appropriato. Si noti che la responsabilità di un processo sarà in gran parte invisibile ai programmatori, poiché l'ambiente di runtime si occupa della maggior parte delle cose.

Ora, per rispondere alle tue domande ...

  1. Nella mia mente, la raccolta dei rifiuti è un passo rimosso da quello che stai facendo qui. Immagino tu stia scrivendo in C, usando malloc()e free(). La garbage collection , ove supportata dal linguaggio di programmazione e dall'ambiente di runtime, si occupa di quest'ultima parte: identifica i blocchi di memoria che erano stati precedentemente allocati ma che non sono più in uso (e, soprattutto, non possono mai essere più utilizzati), e li restituisce all'allocatore. La questione legata a JdeBP ‘s commento fornisce alcuni retroscena, ma trovo lo più interessante perché dimostra che persone diverse hanno opinioni molto diverse sulla raccolta dei rifiuti, e anche ciò che costituisce la raccolta dei rifiuti.

    Nel contesto a cui siamo interessati, utilizzerei la "compattazione della memoria" per parlare del processo in discussione.

  2. Dal punto di vista della programmazione dello spazio utente, ciò che il tuo professore chiede non è possibile, in C, sotto Linux, per una semplice ragione: ciò che ci interessa qui non è la frammentazione della memoria fisica, è la frammentazione dello spazio degli indirizzi. Quando assegni i tuoi numerosi blocchi da 800.000 byte, finirai con altrettanti puntatori a ciascun blocco. Su Linux, a questo punto, il sistema operativo stesso non ha fatto molto, e non hai necessariamente memoria fisica a supporto di ogni allocazione (a parte questo, con allocazioni più piccole il sistema operativo non sarebbe affatto coinvolto, solo il tuo Allocatore della libreria C. Ma le allocazioni qui sono abbastanza grandi da poter essere utilizzate dalla libreria C.mmap, che è gestito dal kernel). Quando si liberano i blocchi dispari, si ottengono quei blocchi di spazio degli indirizzi, ma non è possibile modificare i puntatori che si hanno sugli altri blocchi. Se stampi i puntatori mentre procedi, vedrai che la differenza tra loro non è molto maggiore della richiesta di allocazione (802.816 byte sul mio sistema); non c'è spazio tra due puntatori per un blocco di 900.000 byte. Poiché il programma ha puntatori effettivi su ciascun blocco, piuttosto che un valore più astratto (in altri contesti, un handle), l'ambiente di runtime non può fare nulla al riguardo e quindi non può compattare la sua memoria per fondere blocchi liberi.

    Se usi un linguaggio di programmazione in cui i puntatori non sono un concetto visibile al programmatore, allora la compattazione della memoria è possibile, sotto Linux. Un'altra possibilità sarebbe quella di utilizzare un'API di allocazione della memoria in cui i valori restituiti non sono puntatori; vedere ad esempio le funzioni di allocazione dell'heap basate su handle in Windows (dove i puntatori sono validi solo quando un handle è bloccato).

  3. L'esercizio del tuo professore sta misurando efficacemente le prestazioni di mmap, che include il suo algoritmo di camminata libera. Per prima cosa allocare blocchi 3 × m , quindi liberarne la metà, quindi ricominciare a allocare blocchi m ; liberare tutti quei blocchi scarica un enorme carico di blocchi liberi sull'allocatore del kernel, che deve tenere traccia di (e il tempo impiegato dalle freechiamate mostra che non c'è ottimizzazione in questo momento). Se segui i tempi di allocazione di ogni singolo blocco, vedrai che la prima allocazione da 900k richiede molto, moltopiù lungo degli altri (tre ordini di grandezza sul mio sistema), il secondo è molto più veloce ma impiega ancora molto più tempo (due ordini di grandezza) e la terza allocazione è tornata ai livelli prestazionali tipici. Quindi sta succedendo qualcosa, ma i puntatori restituiti mostrano che non si trattava di compattazione della memoria, almeno non di compattazione dei blocchi allocata (che, come spiegato sopra, è impossibile) - presumibilmente il tempo corrisponde al tempo di elaborazione delle strutture di dati utilizzate dal kernel tenere traccia dello spazio degli indirizzi disponibile nel processo (lo sto verificando e lo aggiornerò in seguito). Queste lunghe allocazioni possono crescere per sminuire le sequenze di allocazione complessive che stai misurando, ovvero quando le allocazioni da 900k finiscono per richiedere più tempo complessivo rispetto alle allocazioni da 800k.

    Il motivo per cui l'overcommit cambia il comportamento che vedi è che cambia l'esercizio dalla semplice manipolazione dello spazio degli indirizzi, all'allocazione effettiva della memoria e quindi riduce le dimensioni del tuo parco giochi. Quando è possibile eseguire il sovraccarico, il kernel è limitato solo dallo spazio degli indirizzi del processo, quindi è possibile allocare molti più blocchi e esercitare molta più pressione sull'allocatore. Quando si disabilita l'overcommit, il kernel è limitato dalla memoria disponibile, il che riduce il valore che si può avere mfino a livelli in cui l'allocatore non è sufficientemente sollecitato da far esplodere i tempi di allocazione.


L'uso di calloc () o la scrittura effettiva negli array allocati farebbe qualche differenza qui?
Kusalananda

Scrivere nella memoria allocata annullerebbe la capacità di overcommit, ma è difficile da gestire perché il fallimento comporta il passaggio di OOM-killer (e non necessariamente l'uccisione del processo di sovrallocazione). calloc()con allocazioni di grandi dimensioni si comporta come malloc()su Linux, utilizzando mmap()per allocare una mappatura anonima, che viene riempita di zero al primo utilizzo (quindi il sovraccarico funziona ancora).
Stephen Kitt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.