Disporre correttamente gli oggetti al termine del server

9

Sto lavorando su un grande progetto C ++. Consiste in un server che espone un'API REST, fornendo un'interfaccia semplice e intuitiva per un sistema molto ampio che comprende molti altri server. Il codebase è abbastanza grande e complesso e si è evoluto nel tempo senza un design adeguato. Il mio compito è implementare nuove funzionalità e refactoring / correggere il vecchio codice per renderlo più stabile e affidabile.

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

La mia idea è di assicurare che tutti gli oggetti siano disposti prima della risoluzione, ma quando ho fatto questa proposta, i miei colleghi e il mio capo mi hanno opposto sottolineando che il sistema operativo libererà comunque quella memoria (che è ovvio per tutti) e lo smaltimento degli oggetti rallenterà l'arresto del server (che al momento è sostanzialmente una chiamata a std::exit ). Ho risposto che avere una procedura di spegnimento "pulita" non implica necessariamente che si debba usarlo. Possiamo sempre chiamare std::quick_exit o solo kill -9 del processo se ci sentiamo impazienti.

Hanno risposto che "la maggior parte dei daemon e dei processi di Linux non si preoccupano di liberare memoria all'arresto". Mentre riesco a vederlo, è anche vero che il nostro progetto ha bisogno di debugging della memoria accurato, in quanto ho già riscontrato problemi di corruzione della memoria, doppie libertà 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? Se sì, perché, e cosa dovrei fare invece?

    
posta Marty 21.12.2014 - 18:43
fonte

5 risposte

7

Aggiungi un passaggio al processo del server che può essere utilizzato durante le misurazioni di valgrind che rilasceranno tutta la memoria. Puoi usare questo interruttore per testare. L'impatto sarà minimo durante le normali operazioni.

Abbiamo avuto un lungo processo in esecuzione che richiederebbe diversi minuti per rilasciare migliaia di oggetti. Era molto più efficiente uscire e lasciarli morire. Sfortunatamente, come tu hai indicato, questo ha reso difficile individuare vere perdite di memoria con valgrind o altri strumenti.

Questo è stato un buon compromesso per i nostri test pur non influenzando le normali prestazioni.

    
risposta data 21.12.2014 - 20:24
fonte
3

La chiave qui è questa:

While I can see that, it is also true that our project does need accurate memory debugging, as I already found memory corruption, double frees and uninitialised variables.

Questo significa direttamente che il tuo codebase è composto insieme da nient'altro che speranza e stringa. I programmatori C ++ competenti non hanno doppia libertà.

Stai assolutamente perseguendo uno sforzo inutile, perché in questo caso 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. Quindi, è chiaramente l'opzione migliore.

    
risposta data 22.12.2014 - 14:12
fonte
2

Un buon approccio sarebbe quello di restringere la discussione con i colleghi attraverso la classificazione. Data una grande base di codice, certamente non c'è una sola ragione, ma piuttosto molteplici, (identificabili) ragioni per oggetti di lunga vita.

Esempi:

  • Oggetti che vivono a lungo e che non sono referenziati da nessuno (perdite reali). È un errore logico di programmazione. Correggete quelli con priorità più bassa A MENO CHE non siano responsabili per il vostro footprint di memoria a crescere nel tempo (e peggiorare la qualità delle vostre applicazioni). Se fanno aumentare il tuo impatto sulla memoria nel tempo, correggili con priorità più alta.

  • Oggetti a lunga vita, che sono ancora referenziati ma non più usati (a causa della logica del programma), ma che non aumentano l'ingombro della memoria. Revisione del codice e prova a trovare gli altri bug che portano a quello. Aggiungi commenti alla base del codice se si tratta di un ottimizzazione intenzionale (prestazioni).

  • Oggetti che vivono a lungo "in base al design". Modello Singleton per esempio. Sono davvero difficili da eliminare, specialmente se si tratta di un'applicazione multi-thread.

  • Oggetti riciclati. Gli oggetti che vivono a lungo non devono sempre essere cattivi. Possono anche essere utili. Invece di disporre di assegnazioni / allocazioni di memoria ad alta frequenza, l'aggiunta di oggetti attualmente non utilizzati a un contenitore da cui attingere quando un nuovo blocco di memoria è necessario può contribuire ad accelerare un'applicazione e ad evitare la frammentazione dell'heap. Dovrebbero essere facili da liberare al momento dello spegnimento, magari in una speciale build "instrumentation / checked".

  • "oggetti condivisi" - oggetti che sono usati (referenziati) da più altri oggetti e nessuno sa esattamente quando è salvato per liberarli. Considera di trasformarli in oggetti conteggiati di riferimento.

Dopo aver classificato le vere ragioni di quegli oggetti non condivisi, è molto più facile inserire una discussione caso per caso e trovare una soluzione.

    
risposta data 22.12.2014 - 08:32
fonte
0

IMHO, la vita di questi oggetti non dovrebbe mai essere fatta e lasciata morire quando il sistema si spegne. Questo puzza di variabili globali, che tutti sappiamo essere male male male male male. Soprattutto nell'era dei puntatori intelligenti, non c'è motivo di fare questo a parte la pigrizia. Ma ancora più importante, aggiunge un livello di debito tecnico al tuo sistema che qualcuno potrebbe dover affrontare un giorno.

L'idea di "debito tecnico" è che quando prendi una scorciatoia come questa, quando qualcuno vuole cambiare il codice in futuro (diciamo, voglio essere in grado di far entrare il client in "modalità offline" o "sleep mode", o voglio essere in grado di cambiare server senza riavviare il processo) dovranno impegnarsi a fare ciò che si sta saltando. Ma manterranno il tuo codice, quindi non ne sapranno più tanto quanto te, quindi ci metteranno molto più a lungo (non parlo più del 20%, sto parlando di 20 volte di più!). Anche se sei tu, allora non avresti lavorato su questo particolare codice per settimane o mesi, e ci vorrà molto più tempo per rispolverare le ragnatele per implementarlo correttamente. L'aumento del tempo che deve essere speso per affrontare decisioni di progettazione sbagliate è l'interesse per il debito tecnico.

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 di un oggetto in un server può essere proibitivo, quindi è meglio che gli oggetti generati siano effettivamente gestiti dall'oggetto server, con salvataggio e aggiornamento delle chiamate che effettivamente cambiano il server. Questo è comunemente chiamato il pattern di registrazione attivo. Vedi:

link

In C ++, avrei ogni record attivo avere un weak_ptr sul server, che potrebbe lanciare le cose in modo intelligente se la connessione al server si oscura. Queste classi possono essere pigramente o popolate, a seconda delle esigenze, ma la durata di tali oggetti deve essere solo nel punto in cui vengono utilizzate.

Vedi anche:

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

L'altro

    
risposta data 21.12.2014 - 20:02
fonte
0

Se è possibile identificare facilmente dove sono allocati gli oggetti che dovrebbero sopravvivere a tempo indefinito, una volta la possibilità sarebbe di allocarli utilizzando un meccanismo di allocazione alternativo in modo che non vengano visualizzati in un rapporto di perdita di valgrind o semplicemente sembrano essere un assegnazione singola.

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

    
risposta data 22.12.2014 - 20:59
fonte

Leggi altre domande sui tag