Smaltire correttamente gli oggetti al termine del server


9

Sto lavorando a un grande progetto C ++. Consiste in un server che espone un'API REST, fornendo un'interfaccia semplice e intuitiva per un sistema molto ampio comprendente molti altri server. La base di codice è piuttosto ampia e complessa e si è evoluta nel tempo senza un adeguato design iniziale. Il mio compito è implementare nuove funzionalità e refactoring / correggere il vecchio codice al fine di renderlo più stabile e affidabile.

Al momento, il server crea un numero di oggetti di lunga durata che non vengono mai terminati né eliminati al termine del processo. Ciò rende Valgrind quasi inutilizzabile per il rilevamento delle perdite, poiché è impossibile distinguere tra le migliaia di (legittimamente) perdite legittime da quelle "pericolose".

La mia idea è quella di garantire che tutti gli oggetti siano eliminati prima della risoluzione, ma quando ho fatto questa proposta, i miei colleghi e il mio capo si sono opposti a me sottolineando che il sistema operativo libererà comunque quella memoria (che è ovvio per tutti) e disponendo gli oggetti rallenterà l'arresto del server (che, al momento, è fondamentalmente una chiamata a std::exit). Ho risposto che avere una procedura di spegnimento "pulita" non implica necessariamente che si debba usarla. Possiamo sempre chiamare std::quick_exito semplicemente kill -9il processo se ci sentiamo impazienti.

Hanno risposto "la maggior parte dei demoni e dei processi Linux non si preoccupano di liberare memoria allo spegnimento". Anche se posso vederlo, è anche vero che il nostro progetto ha bisogno di un accurato debug della memoria, poiché ho già trovato corruzione della memoria, doppia liberazione e variabili non inizializzate.

Quali sono i tuoi pensieri? Sto perseguendo uno sforzo inutile? In caso contrario, come posso convincere i miei colleghi e il mio capo? In tal caso, perché e cosa devo fare invece?


Oltre all'argomento delle prestazioni (che è ragionevole!), È molto difficile isolare gli oggetti di lunga durata e aggiungere loro un codice di pulizia?
Doc Brown,

Risposte:


7

Aggiungere un interruttore al processo del server che può essere utilizzato durante le misurazioni di valgrind che rilascerà tutta la memoria. È possibile utilizzare questa opzione per i test. L'impatto sarà minimo durante le normali operazioni.

Abbiamo avuto un lungo processo che avrebbe richiesto diversi minuti per rilasciare migliaia di oggetti. Era molto più efficiente uscire e lasciarli morire. Sfortunatamente, come indichi, questo ha reso difficile rilevare vere perdite di memoria con valgrind o altri strumenti.

Questo è stato un buon compromesso per i nostri test senza influire sulle prestazioni normali.


1
+1 Pragmatismo FTW. C'è valore nella misurazione, ma c'è anche valore nello spegnimento rapido.
Ross Patterson,

2
In alternativa a un'opzione della riga di comando, potresti anche prendere in considerazione l'implementazione dell'eliminazione degli oggetti permanenti all'interno di un blocco DEBUG #ifdef.
Jules il

3

La chiave qui è questa:

Anche se posso vederlo, è anche vero che il nostro progetto ha bisogno di un accurato debug della memoria, poiché ho già trovato corruzione della memoria, doppia liberazione e variabili non inizializzate.

Questo praticamente implica direttamente che il tuo codebase è messo insieme da niente più che speranza e stringa. I programmatori C ++ competenti non hanno doppi frees.

Stai assolutamente perseguendo uno sforzo inutile, come in, stai affrontando un piccolo sintomo del problema reale, ovvero che il tuo codice è affidabile quanto il modulo di servizio Apollo 13.

Se si programma correttamente il server con RAII, questi problemi non si verificheranno e il problema nella domanda verrà eliminato. Inoltre, il tuo codice potrebbe effettivamente essere eseguito correttamente di volta in volta. Pertanto, è chiaramente l'opzione migliore.


Certamente il problema sta nel quadro più ampio. Tuttavia, è molto raro che si possano trovare risorse e permessi per riformattare / riscrivere un progetto per modellare meglio.
Cengiz può il

@CengizCan: se vuoi correggere i bug, devi refactoring. Funziona così.
DeadMG

2

Un buon approccio sarebbe quello di restringere la discussione con i tuoi colleghi mediante la classificazione. Data una base di codice di grandi dimensioni, certamente non esiste una sola ragione ma piuttosto molteplici ragioni (identificabili) per oggetti longevi.

Esempi:

  • Oggetti longevi a cui non fa riferimento nessuno (perdite reali). È un errore logico di programmazione. Risolvi quelli con priorità inferiore A MENO CHE non siano responsabili della tua impronta di memoria nel tempo (e deteriorano la qualità delle tue applicazioni). Se aumentano il tuo footprint di memoria nel tempo, correggili con una priorità più elevata.

  • Oggetti longevi, a cui si fa ancora riferimento ma che non vengono più utilizzati (a causa della logica del programma), ma che non fanno crescere la memoria. Revisione del codice e prova a trovare gli altri bug che portano a questo. Aggiungi commenti alla base di codice se si tratta di un'ottimizzazione (delle prestazioni) intenzionale.

  • Oggetti longevi "di design". Modello Singleton per esempio. Sono davvero difficili da eliminare, specialmente se si tratta di un'applicazione multi-thread.

  • Oggetti riciclati. Gli oggetti di lunga durata non devono sempre essere cattivi. Possono anche essere utili. Invece di disporre di allocazioni / deallocazioni di memoria ad alta frequenza, l'aggiunta di oggetti attualmente inutilizzati a un contenitore da cui attingere quando è necessario un tale blocco di memoria può aiutare ad accelerare un'applicazione ed evitare la frammentazione dell'heap. Dovrebbero essere facili da liberare al momento dello spegnimento, magari in una speciale build "strumentazione / controllata".

  • "oggetti condivisi": oggetti che vengono utilizzati (referenziati) da più altri oggetti e nessuno sa esattamente quando viene salvato per liberarli. Considera di trasformarli in oggetti contati di riferimento.

Una volta classificate le vere ragioni di quegli oggetti non rigati, è molto più facile inserire una discussione caso per caso e trovare una soluzione.


0

IMHO, le vite di questi oggetti non dovrebbero mai essere fatte e lasciate morire quando il sistema si spegne. Questo puzza di variabili globali, che sappiamo tutti sono cattive, cattive, cattive, cattive. Soprattutto nell'era dei puntatori intelligenti, non c'è motivo di farlo se non la pigrizia. Ma soprattutto, aggiunge un livello di debito tecnico al tuo sistema che qualcuno potrebbe dover affrontare un giorno.

L'idea di "debito tecnico" è che quando si prende una scorciatoia come questa, quando qualcuno vuole cambiare il codice in futuro (diciamo, voglio essere in grado di far passare il client in "modalità offline" o "modalità sleep" ", o voglio poter cambiare server senza riavviare il processo) dovranno impegnarsi per fare ciò che si sta saltando. Ma manterranno il tuo codice, quindi non ne sapranno quasi quanto te, quindi impiegheranno molto più tempo (non parlerò più del 20%, parlerò 20 volte più!). Anche se sei tu, allora non avrai lavorato su questo particolare codice per settimane o mesi e ci vorrà molto più tempo per rispolverare le ragnatele per implementarlo correttamente.

In questo caso, sembra che tu abbia un accoppiamento molto stretto tra l'oggetto server e gli oggetti "longevi" ... ci sono momenti in cui un record potrebbe (e dovrebbe) sopravvivere alla connessione al server. Il mantenimento di ogni singola modifica a un oggetto in un server può essere proibitivo, quindi di solito è meglio che gli oggetti generati vengano effettivamente sottoposti a chakra dell'oggetto server, con le chiamate di salvataggio e aggiornamento che cambiano effettivamente il server. Questo è comunemente chiamato modello di record attivo. Vedere:

http://en.wikipedia.org/wiki/Active_record_pattern

In C ++, ogni record attivo avrebbe un debole_ptr sul server, che potrebbe gettare le cose in modo intelligente se la connessione al server si oscura. Queste classi possono essere pigramente o popolate in lotti, a seconda delle tue esigenze, ma la durata di tali oggetti dovrebbe essere solo dove vengono utilizzati.

Guarda anche:

È una perdita di tempo liberare risorse prima di uscire da un processo?

L'altro


This reeks of global variablesCome vai da "ci sono migliaia di oggetti che devono essere liberati" a "devono essere globali"? È un bel salto di logica.
Doval,

0

Se riesci a identificare facilmente dove sono allocati gli oggetti che dovrebbero sopravvivere indefinitamente, una volta sarebbe possibile allocarli utilizzando un meccanismo di allocazione alternativo in modo che non vengano visualizzati in un rapporto perdite Valgrind o appaiano semplicemente come un'unica allocazione.

Nel caso in cui non si abbia familiarità con l'idea, ecco un articolo su come impotente allocazione di memoria personalizzata in c ++ , sebbene si noti che la soluzione potrebbe essere più semplice degli esempi in quell'articolo in quanto non è necessario gestire la cancellazione affatto !

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.