GNU/Linux >> Linux Esercitazione >  >> Linux

L'allocazione della memoria in Linux non è bloccante?

mi sembra che se la tua app di interferenza stesse usando new/delete (malloc/free), allora l'app di interferenza interferirebbe maggiormente con il test di non riciclo. Ma non so come sia implementato il tuo test di interferenza.

A seconda di come ricicli (ad esempio se usi pthread mutex, Dio non voglia), il tuo codice di riciclo potrebbe essere lento (le operazioni atomiche di gcc sarebbero 40 volte più veloci nell'implementare il riciclo).

Malloc, in alcune varianti da molto tempo almeno su alcune piattaforme, è a conoscenza dei thread. Usa gli switch del compilatore su gcc per essere sicuro di averlo. Gli algoritmi più recenti mantengono pool di piccoli blocchi di memoria per ciascuno thread, quindi non c'è o c'è poco blocco se il tuo thread ha l'elemento piccolo disponibile. L'ho semplificato eccessivamente e dipende da quale malloc sta utilizzando il tuo sistema. Inoltre, se assegni milioni di oggetti per fare un test... beh, allora non vedrai quell'effetto, perché i piccoli pool di oggetti sono di dimensioni limitate. O forse lo farai. Non lo so. Se hai liberato l'oggetto subito dopo l'allocazione, è più probabile che tu lo veda. Gli elementi piccoli liberati tornano negli elenchi di elementi piccoli anziché nell'heap condiviso. Sebbene "cosa succede quando il thread B libera un elemento assegnato dal thread A" è un problema che può o meno essere affrontato nella tua versione di malloc e potrebbe non essere affrontato in modo non bloccante. Di sicuro, se non ti sei liberato immediatamente durante un test di grandi dimensioni, il thread dovrebbe riempire molte volte il suo piccolo elenco di elementi. Ciò può bloccarsi se più di un thread tenta. Infine, a un certo punto l'heap del tuo processo chiederà al sistema la memoria dell'heap, che può ovviamente bloccarsi.

Quindi stai usando piccoli oggetti di memoria? Per il tuo malloc non so quanto sarebbe piccolo, ma se sei <1k è sicuramente piccolo. Stai allocando e liberando uno dopo l'altro o allocando migliaia di nodi e poi liberando migliaia di nodi? La tua app di interferenza stava assegnando? Tutte queste cose influenzeranno i risultati.

Come riciclare con le operazioni atomiche (CAS =confronta e scambia):

Per prima cosa aggiungi un pNextFreeNode al tuo oggetto nodo. Ho usato void*, puoi usare il tuo tipo. Questo codice è per puntatori a 32 bit, ma funziona anche per 64 bit. Quindi crea una pila di riciclaggio globale.

void *_pRecycleHead; // global head of recycle list. 

Aggiungi alla pila dei rifiuti:

void *Old;
while (1) { // concurrency loop
  Old = _pRecycleHead;  // copy the state of the world. We operate on the copy
  pFreedNode->pNextFreeNode = Old; // chain the new node to the current head of recycled items
  if (CAS(&_pRecycleHead, Old, pFreedNode))  // switch head of recycled items to new node
    break; // success
}

rimuovi dalla pila:

void *Old;
while (Old = _pRecycleHead) { // concurrency loop, only look for recycled items if the head aint null
  if (CAS(&_pRecycleHead, Old, Old->pNextFreeNode))  // switch head to head->next.
    break; // success
}
pNodeYoucanUseNow = Old;

L'uso di CAS significa che l'operazione avrà successo solo se l'elemento che stai modificando è il vecchio valore che passi. Se c'è una gara e un altro thread è arrivato prima, allora il vecchio valore sarà diverso. Nella vita reale questa gara accade molto molto raramente. CAS è solo leggermente più lento rispetto all'impostazione effettiva di un valore così rispetto ai mutex.... spacca.

Il rimuovi dalla pila, sopra, ha una race condition se aggiungi e rimuovi rapidamente lo stesso oggetto. Lo risolviamo aggiungendo una versione # ai dati compatibili con CAS. Se fai la versione # contemporaneamente al puntatore alla testa della pila di riciclo, vinci. Usa un sindacato. Non costa nulla in più rispetto a CAS 64 bit.

union TRecycle {
  struct {
    int iVersion;
    void *pRecycleHead;
  } ;  // we can set these.  Note, i didn't name this struct.  You may have to if you want ANSI
  unsigned long long n64;  // we cas this
}

Nota, dovrai andare alla struttura a 128 bit per il sistema operativo a 64 bit. quindi la pila di riciclo globale ora ha questo aspetto:

TRecycle _RecycleHead;

Aggiungi alla pila dei rifiuti:

while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  pFreedNode->pNextFreeNode = Old.pRecycleHead;  // link item to be recycled into recycle pile
  New.pRecycleHead = pFreedNode;  // make the new state
  New.iVersion++;  // adding item to list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // now if version changed...we fail
    break; // success
}

rimuovi dalla pila:

while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  New.pRecycleHead = New.pRecycledHead.pNextFreeNode;  // new will skip over first item in recycle list so we can have that item.
  New.iVersion++;  // taking an item off the list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // we fail if version is different.
    break; // success
}
pNodeYouCanUseNow = Old.pRecycledHead;

Scommetto che se ricicli in questo modo vedrai un aumento delle prestazioni.


Questa domanda ha una serie di buone risposte:in C/C++ multithread, malloc/new blocca l'heap durante l'allocazione della memoria.

Il consenso è che ci sia un blocco. Quindi un'allocazione grande o che richiede uno scambio potrebbe bloccare un'allocazione più piccola in un altro thread, anche se quella più piccola potrebbe finire se non fosse per l'allocazione più grande in corso.

gcc's new è thread-safe, se compili con il supporto pthreads, ma non è proprio quello che stai chiedendo.

So che in Windows puoi creare il tuo heap, che potrebbe essere utilizzato per impostare la memoria all'inizio del tuo programma. Non sono a conoscenza di alcuna chiamata linux/unix per fare cose simili.


Questo è praticamente lo stesso di questa domanda.

Fondamentalmente, malloc non è definito per essere thread-safe, ma gli implementatori sono liberi di aggiungere l'implementazione per renderlo thread-safe. Dalla tua descrizione, sembra che la tua versione particolare lo sia.

A dire il vero, nelle parole di Obi-Wan, "Usa la fonte, Luke". Il malloc fonte sarà in giro ed è generalmente piuttosto semplice da leggere.

@Mark, puoi ottenere il sorgente GNU libc standard tramite

$ git clone git://sourceware.org/git/glibc.git
$ cd glibc
$ git checkout --track -b glibc-2_11-branch origin/release/2.11/master

Vedi anche qui. Ricorda quel malloc è nella sezione 3 del manuale:è una funzione di libreria, quindi non sarà nei sorgenti del kernel. Potrebbe, tuttavia, essere necessario leggere fino a brk ,sbrk , getrlimit e setrlimit e simili per scoprire cosa fa il kernel.

Un altro collegamento:il progetto GCC.

Va bene, ancora una volta (posso fermarmi in qualsiasi momento):ecco una pagina da cui puoi scaricare i sorgenti. Decomprimi il file e dovresti trovarlo in ./malloc/malloc.c .


Nei sistemi multithread, malloc() e free() (e new / delete ) in genere utilizzano primitive di sincronizzazione per renderle sicure da chiamare da più thread.

Questa sincronizzazione influisce anche sulle prestazioni di alcune applicazioni, in particolare applicazioni che eseguono molte allocazioni e deallocazioni in ambienti altamente paralleli. Gli allocatori di memoria multithreading più efficienti sono un campo di ricerca attivo - vedi jemalloc e tcmalloc per due famosi.


Linux
  1. Utilizzo della memoria di Linux

  2. Come cancellare la cache di memoria in Linux

  3. Linux:reale utilizzo della memoria?

  4. Killer di memoria insufficiente di Linux

  5. Versione Kali Linux 2018.1

Come controllare l'utilizzo della memoria in Linux

Comando gratuito Linux (controlla l'utilizzo della memoria)

Comando gratuito in Linux

Come cancellare la memoria di scambio in Linux

Esempi di comandi gratuiti in Linux

Segmentazione della memoria di Linux