GNU/Linux >> Linux Esercitazione >  >> Linux

L'eliminazione di hashmap di grandi dimensioni con milioni di stringhe su un thread influisce sulle prestazioni su un altro thread

Potrebbe essere utile memorizzare un solo std::string per tutti i tuoi dati combinati e usa std::string_view nella mappa. Ciò elimina la contesa mutex poiché è necessaria solo un'allocazione di memoria. string_view ha un banale distruttore quindi non hai bisogno di un thread per quello.

Ho già utilizzato con successo questa tecnica per velocizzare un programma del 2500%, ma anche perché questa tecnica ha ridotto l'utilizzo totale della memoria.


Puoi provare a usare un std::vector per conservare la memoria. std::vector gli elementi sono memorizzati in modo contiguo, quindi ridurrà la mancanza di cache (vedi Cos'è un codice "cache-friendly"?)

Quindi avrai un map<???,size_t> invece di map<???,std::string> avrai un'ulteriore indiretta per ottenere la tua stringa (il che significa un costo di esecuzione extra) ma ti permetterà di iterare su tutte le stringhe con molto meno cache-miss.


Sarebbe bello se ricreassi il problema che stai riscontrando con un MVCE e lo mostrassi:sai, molte volte il problema che stai pensando è il tuo problema... non è il problema.

Come posso trovare con certezza che i 2 problemi di memoria di cui sopra sono la causa (qualsiasi strumento/metrica?)

Date le informazioni qui, suggerirei di utilizzare un profiler - gprof (compilare con -g -pg) è quello di base. Se hai a disposizione il compilatore Intel puoi usare vtune.

Esiste una versione gratuita di vtune ma personalmente ho utilizzato solo la versione commerciale.

Oltre a questo puoi inserire i tempi nel tuo codice:dalla descrizione testuale, non è chiaro se il tempo per popolare la mappa sia paragonabile al tempo necessario per cancellarla, o se cresce costantemente quando viene eseguito contemporaneamente. Inizierei con se. Si noti che l'attuale versione di malloc() è notevolmente ottimizzata anche per la concorrenza (è questo Linux? - aggiungere un tag alla domanda per favore).

Di sicuro quando cancelli la mappa ci sono milioni di free() è chiamato da std::~string() - ma devi essere sicuro che questo sia il problema o meno:puoi utilizzare un approccio migliore (molti citati nelle risposte/commenti) o un allocatore personalizzato supportato da un enorme blocco di memoria che crei/distruggi come una singola unità.

Se fornisci un MVCE come punto di partenza, io o altri saremo in grado di fornire una risposta coerente (questa non è ancora una risposta, ma è troppo lunga per essere un commento)

Giusto per chiarire, il programma deliberatamente non alloca mai cose e ne libera altre allo stesso tempo, e ha solo 2 thread, uno dedicato solo alla cancellazione.

Tieni presente che ogni stringa nella mappa necessita di uno (o più) new e un delete (basato su malloc() e free() rispettivamente), essendo le stringhe nelle chiavi o nei valori.

Cosa hai nei "valori" della mappa?

Dato che hai un map<string,<set<int>> hai molte allocazioni:ogni volta che esegui un map[string].insert(val) di una nuova chiave, il tuo codice chiama implicitamente malloc() sia per la corda che per l'insieme. Anche se la chiave è già nella mappa, un nuovo int nel set richiede l'allocazione di un nuovo nodo nel set.

Quindi hai davvero molte allocazioni mentre costruisci la struttura:la tua memoria è molto frammentata su un lato, e il tuo codice sembra davvero "malloc intensive", il che potrebbe in linea di principio portare le chiamate di memoria a morire di fame.

Allocazioni/deallocazioni di memoria multithread

Una particolarità dei moderni sottosistemi di memoria è che sono ottimizzati per sistemi multi-core:quando un thread alloca memoria su un core, non c'è un blocco globale, ma un blocco thread-local o core-local per un pool thread-local .

Ciò significa che quando un thread deve liberare la memoria allocata da un altro, è coinvolto un blocco non locale (più lento).

Ciò significa che l'approccio migliore è che ogni thread allochi/dealloca la propria memoria. Detto che in linea di principio puoi ottimizzare molto il tuo codice con strutture di dati che richiedono meno interazioni malloc/libere, il tuo codice sarà più locale, rispetto alle allocazioni di memoria, se lasci che ogni thread:

  • ottieni un blocco di dati
  • crea il map<string,<set<int>>
  • liberalo

E hai due thread che eseguono ripetutamente questa operazione.

NOTA:hai bisogno di abbastanza RAM per gestire i valutatori simultanei, ma ora ne stai già utilizzando 2 caricati contemporaneamente con uno schema di doppio buffering (un riempimento, una pulizia). Sei sicuro che il tuo sistema non stia effettuando lo swapping a causa dell'esaurimento della RAM?

Inoltre, questo approccio è scalabile:puoi usare tutti i thread che vuoi. Nel tuo approccio eri limitato a 2 thread:uno che costruisce la struttura, l'altro che la distrugge.

Ottimizzazione

Senza un MVCE è un compito difficile dare indicazioni. Solo idee che sai solo se possono essere applicate ora:

  • sostituisci l'insieme con il vettore ordinato, riservato al momento della creazione
  • sostituisci le chiavi della mappa con un vettore piatto di stringhe ordinate equidistanti
  • memorizza le chiavi della stringa in sequenza in un vettore piatto, aggiungi hash per tenere traccia delle chiavi della mappa. Aggiungi una hash-map per tenere traccia dell'ordine delle stringhe nel vettore.

Linux
  1. Eseguire una linea di comandi con un sudo?

  2. Eliminare tutti i commenti C con Sed?

  3. Il comando gratuito in Linux spiegato con esempi

  4. Accedi al thread locale da un altro thread

  5. sostituire le righe in un file con le righe in un altro per numero di riga

Come sostituire una distribuzione Linux con un'altra dal dual boot [mantenere la partizione home]

Rileva librerie condivise obsolete in memoria con UChecker

Analisi delle prestazioni del server Linux con atop

pytest in esecuzione con un'altra versione di python

Come sostituire tutti i contenuti di una cartella con un'altra

Configurazione di DRBD con un solo nodo