Domanda dell'ingegnere di livello base per quanto riguarda la gestione della memoria


9

Sono passati alcuni mesi da quando ho iniziato la mia posizione come sviluppatore software entry-level. Ora che ho superato alcune curve di apprendimento (ad esempio la lingua, il gergo, la sintassi di VB e C #) sto iniziando a concentrarmi su argomenti più esoterici, per scrivere software migliore.

Una semplice domanda che ho presentato a un collega collega ha risposto "Mi sto concentrando su cose sbagliate". Mentre rispetto questo collega, non sono d'accordo sul fatto che questa sia una "cosa sbagliata" su cui concentrarsi.

Ecco il codice (in VB) e seguito dalla domanda.

Nota: la funzione GenerateAlert () restituisce un numero intero.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

vs ...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

Originariamente ho scritto quest'ultimo e l'ho riscritto con "Dim alertID" in modo che qualcun altro potesse trovarlo più facile da leggere. Ma ecco la mia preoccupazione e domanda:

Se si dovesse scrivere questo con Dim AlertID, occuperebbe effettivamente più memoria; finito ma altro, e questo metodo dovrebbe essere chiamato molte volte potrebbe portare a un problema? In che modo .NET gestirà questo oggetto AlertID. Al di fuori di .NET, è necessario disporre manualmente dell'oggetto dopo l'uso (vicino alla fine del sottotitolo).

Voglio assicurarmi di diventare un programmatore esperto che non si affida solo alla raccolta dei rifiuti. Sto pensando troppo? Mi sto concentrando su cose sbagliate?


1
Potrei facilmente affermare che è al 100% poiché la prima versione è più leggibile. Scommetto che il compilatore potrebbe anche prendersi cura di ciò che ti interessa. Anche se non lo fosse, stai ottimizzando prematuramente.
Rig

6
Non sono affatto sicuro che utilizzerebbe davvero più memoria con un numero intero anonimo rispetto a un numero intero con nome. In ogni caso, questa è davvero un'ottimizzazione prematura. Se devi preoccuparti dell'efficienza a questo livello (sono quasi sicuro di no) potresti aver bisogno di C ++ invece di C #. È bene capire i problemi di prestazioni e cosa succede sotto il cofano, ma questo è un piccolo albero in una grande foresta.
ps

5
L'intero con nome vs anonimo non userebbe più memoria, soprattutto perché un intero anonimo è solo un intero con nome che NON hai nominato (il compilatore deve ancora nominarlo). Al massimo, il numero intero denominato avrebbe un ambito diverso, quindi potrebbe vivere più a lungo. L'intero anonimo vivrebbe solo finché il metodo ne avesse bisogno, quello nominato vivrà fino a quando il suo genitore ne avesse bisogno.
Joel Etherton,

Vediamo ... Se Integer è una classe, verrà allocata nell'heap. La variabile locale (molto probabilmente nello stack) conterrà un riferimento ad essa. Il riferimento verrà passato all'oggetto errorDictionary. Se il runtime sta eseguendo il conteggio dei riferimenti (o simili), quando non ci sono più riferimenti, esso (l'oggetto) verrà deallocato dall'heap. Qualsiasi cosa nello stack viene automaticamente "deallocata" una volta terminato il metodo. Se è una primitiva, (molto probabilmente) finirà in pila.
Paul

Il tuo collega aveva ragione: il problema sollevato dalla tua domanda non avrebbe dovuto riguardare l'ottimizzazione, ma la leggibilità .
Hayylem,

Risposte:


25

"L'ottimizzazione prematura è la radice di tutti i mali (o almeno la maggior parte di essi) nella programmazione." - Donald Knuth

Quando si tratta del primo passaggio, basta scrivere il codice in modo che sia corretto e pulito. Se in seguito viene stabilito che il codice è critico per le prestazioni (esistono strumenti per determinare questo chiamato profiler), può essere riscritto. Se il tuo codice non è considerato critico per le prestazioni, la leggibilità è molto più importante.

Vale la pena approfondire questi argomenti di prestazioni e ottimizzazione? Assolutamente, ma non sul dollaro della tua azienda se non è necessario.


1
Su chi altro dollaro dovrebbe essere? Il tuo datore di lavoro beneficia dell'aumento delle tue capacità piuttosto che di te.
Marcin,

Soggetti che non contribuiscono direttamente al tuo attuale compito? Dovresti perseguire queste cose nel tuo tempo libero. Se mi sedessi e cercassi ogni oggetto CompSci che ha suscitato la mia curiosità nel corso della giornata, non avrei fatto nulla. Ecco a cosa servono le mie serate.
Christopher Berman,

2
Strano. Alcuni di noi hanno una vita personale e, come ho detto, il datore di lavoro beneficia principalmente della ricerca. La chiave è non spendere tutto il giorno su di esso.
Marcin,

6
Buon per te. In realtà non lo rende una regola universale. Inoltre, se scoraggi i tuoi lavoratori dall'apprendimento sul lavoro, tutto ciò che hai fatto è scoraggiarli dall'apprendimento e li hai incoraggiati a trovare un altro datore di lavoro che effettivamente paga per lo sviluppo del personale.
Marcin,

2
Comprendo le opinioni annotate nei commenti sopra; Vorrei notare che l'ho chiesto durante la pausa pranzo. :). Ancora una volta, grazie a tutti per il vostro contributo qui e in tutto il sito Stack Exchange; è inestimabile!
Sean Hobbs,

5

Per il tuo programma .NET medio, sì, sta pensando troppo. Ci possono essere momenti in cui ti consigliamo di capire esattamente cosa sta succedendo all'interno di .NET, ma questo è relativamente raro.

Una delle transizioni difficili che ho avuto è stato il passaggio dall'uso di C e MASM alla programmazione nel classico VB negli anni '90. Ero abituato a ottimizzare tutto per dimensioni e velocità. Ho dovuto abbandonare questo pensiero per la maggior parte e lasciare che VB facesse la sua cosa per essere efficace.


5

Come diceva sempre il mio collega:

  1. Fallo funzionare
  2. Risolvi tutti i bug che lo rendono perfetto
  3. Rendilo SOLID
  4. Applica l'ottimizzazione se sta eseguendo lentamente

In altre parole, tieni sempre presente il BACIO (mantienilo stupido). Perché l'eccessiva ingegneria, l'eccessiva riflessione sulla logica del codice può essere un problema per cambiare la logica la prossima volta. Tuttavia, mantenere il codice pulito e semplice è sempre una buona pratica .

Tuttavia, per tempo ed esperienza, sapresti meglio quale codice ha un odore e necessiterebbe di ottimizzazione abbastanza presto.


3

Si dovrebbe scrivere questo con Dim AlertID

La leggibilità è importante. Nel tuo esempio, però, non sono sicuro che si sta davvero facendo le cose che più leggibile. GenerateAlert () ha un buon nome e non aggiunge molto rumore. Probabilmente ci sono usi migliori del tuo tempo.

in effetti occuperebbe più memoria;

Sospetto che non lo sia. È un'ottimizzazione relativamente semplice da eseguire per il compilatore.

questo metodo dovrebbe essere chiamato molte volte potrebbe portare a un problema?

L'uso di una variabile locale come intermediario non ha alcun impatto sul Garbage Collector. Se la memoria di GenerateAlert () è nuova, sarà importante. Ma questo avrà importanza indipendentemente dalla variabile locale o meno.

In che modo .NET gestirà questo oggetto AlertID.

AlertID non è un oggetto. Il risultato di GenerateAlert () è l'oggetto. AlertID è la variabile, che se si tratta di una variabile locale è semplicemente lo spazio associato al metodo per tenere traccia delle cose.

Al di fuori di .NET è necessario disporre manualmente dell'oggetto dopo l'uso

Questa è una domanda più personale che dipende dal contesto coinvolto e dalla semantica della proprietà dell'istanza fornita da GenerateAlert (). In generale, qualunque cosa abbia creato l'istanza dovrebbe eliminarla. Il tuo programma sarebbe probabilmente molto diverso se fosse progettato pensando alla gestione manuale della memoria.

Voglio assicurarmi di diventare un programmatore esperto che non si limita a ricorrere alla raccolta dei rifiuti. Sto pensando troppo? Mi sto concentrando su cose sbagliate?

Un buon programmatore usa gli strumenti a loro disposizione, incluso il garbage collector. È meglio pensare alle cose piuttosto che vivere nell'oblio. Potresti concentrarti su cose sbagliate, ma dal momento che siamo qui, potresti anche conoscerlo.


2

Fallo funzionare, rendilo pulito, rendilo SOLIDO, quindi fallo funzionare il più velocemente possibile .

Questo dovrebbe essere il normale ordine delle cose. La tua prima priorità è fare qualcosa che superi i test di accettazione che scuotono i requisiti. Questa è la tua prima priorità perché è la prima priorità del tuo cliente; soddisfare i requisiti funzionali entro i termini di sviluppo. La prossima priorità è scrivere un codice pulito e leggibile che sia facile da capire e possa quindi essere mantenuto dai posteri senza alcun WTF quando questo diventa necessario (non è quasi mai una questione di "se"; tu o qualcuno dopo te dovrete andare rientrare e cambiare / correggere qualcosa). La terza priorità è far aderire il codice alla metodologia SOLID (o GRASP se preferisci), che inserisce il codice in blocchi modulari, riutilizzabili e sostituibili che aiutano di nuovo la manutenzione (non solo possono capire cosa hai fatto e perché, ma ci sono linee pulite lungo le quali posso rimuovere chirurgicamente e sostituire pezzi di codice). L'ultima priorità è la prestazione; se il codice è abbastanza importante da essere conforme alle specifiche di prestazione, è quasi certamente abbastanza importante da essere corretto, pulito e SOLID prima.

Facendo eco a Christopher (e Donald Knuth), "l'ottimizzazione prematura è la radice di tutti i mali". Inoltre, il tipo di ottimizzazioni che stai prendendo in considerazione sono sia minori (un riferimento al tuo nuovo oggetto verrà creato nello stack sia che tu gli dia un nome nel codice sorgente o meno) e di un tipo che non può causare alcuna differenza nella compilazione I L. I nomi delle variabili non vengono riportati nell'IL, quindi dal momento che stai dichiarando la variabile prima del suo primo (e probabilmente solo) utilizzo, scommetterei un po 'di soldi per la birra che l'IL è identico tra i tuoi due esempi. Quindi, il tuo collega ha ragione al 100%; stai cercando nel posto sbagliato se stai cercando un'istanza variabile denominata vs inline per qualcosa da ottimizzare.

Le micro-ottimizzazioni in .NET non valgono quasi mai la pena (sto parlando del 99,99% dei casi). In C / C ++, forse, SE sai cosa stai facendo. Quando si lavora in un ambiente .NET, si è già abbastanza lontani dal metallo dell'hardware da presentare un notevole sovraccarico nell'esecuzione del codice. Quindi, dato che sei già in un ambiente che indica che hai rinunciato alla velocità della vescica e stai invece cercando di scrivere codice "corretto", se qualcosa in un ambiente .NET non funziona davvero abbastanza velocemente, o la sua complessità è troppo alto o dovresti considerare di parallelizzarlo. Ecco alcuni suggerimenti di base da seguire per l'ottimizzazione; Ti garantisco che la tua produttività nell'ottimizzazione (la velocità guadagnata per il tempo speso) salirà alle stelle:

  • La modifica della forma della funzione è più importante della modifica dei coefficienti : complessità WRT Big-Oh, è possibile ridurre della metà il numero di passaggi che devono essere eseguiti in un algoritmo N 2 e si ha ancora un algoritmo di complessità quadratica anche se viene eseguito in metà del tempo. Se questo è il limite inferiore della complessità per questo tipo di problema, così sia, ma se esiste una soluzione NlogN, lineare o logaritmica allo stesso problema, otterrai di più cambiando algoritmi per ridurre la complessità che ottimizzando quello che hai.
  • Solo perché non riesci a vedere la complessità non significa che non ti stia costando - Molti dei più eleganti one-liner nella parola funzionano terribilmente (ad esempio, il controllore principale Regex è una funzione di complessità esponenziale, mentre efficiente la valutazione primaria che prevede la divisione del numero per tutti i numeri primi inferiori alla sua radice quadrata è nell'ordine di O (Nlog (sqrt (N))). Linq è un'ottima libreria perché semplifica il codice, ma a differenza di un motore SQL, il .Net il compilatore non cercherà di trovare il modo più efficiente di eseguire la tua query. Devi sapere cosa accadrà quando usi un metodo e quindi perché un metodo potrebbe essere più veloce se collocato prima (o successivamente) nella catena, mentre produce gli stessi risultati.
  • OTOH, c'è quasi sempre un compromesso tra complessità della sorgente e complessità del runtime - SelectionSort è molto facile da implementare; probabilmente potresti farlo in 10LOC o meno. MergeSort è un po 'più complesso, Quicksort ancora di più e RadixSort ancora di più. Tuttavia, quando gli algoritmi aumentano nella complessità della codifica (e quindi nel tempo di sviluppo "iniziale"), diminuiscono nella complessità del tempo di esecuzione; MergeSort e QuickSort sono NlogN e RadixSort è generalmente considerato lineare (tecnicamente è NlogM dove M è il numero più grande in N).
  • Break fast - Se c'è un controllo che può essere fatto a buon mercato che è significativamente probabile che sia vero e significa che puoi andare avanti, esegui prima quel controllo. Se il tuo algoritmo, ad esempio, si preoccupa solo dei numeri che terminano con 1, 2 o 3, il caso più probabile (dati dati completamente casuali) è un numero che termina in qualche altra cifra, quindi verifica che il numero NON finisca tra 1, 2 o 3, prima di effettuare qualsiasi controllo per vedere se il numero termina in 1, 2 o 3. Se un pezzo di logica richiede A&B e P (A) = 0.9 mentre P (B) = 0.1, quindi controlla B prima, a meno che se! A poi! B (come if(myObject != null && myObject.someProperty == 1)), o B impiega più di 9 volte di più di A per valutare ( if(myObject != null && some10SecondMethodReturningBool())).
  • Non porre alcuna domanda a cui conosci già la risposta - Se hai una serie di condizioni "fall-through" e una o più di tali condizioni dipendono da una condizione più semplice che deve essere verificata, non controllare mai entrambe questi indipendentemente. Ad esempio, se hai un controllo che richiede A e un controllo che richiede A & B, dovresti controllare A, e se vero dovresti controllare B. Se! A, allora! A && B, quindi non preoccuparti nemmeno.
  • Più volte fai qualcosa, più dovresti prestare attenzione a come è fatto - Questo è un tema comune nello sviluppo, a molti livelli; in senso generale di sviluppo, "se un compito comune richiede tempo o è difficile, continua a farlo fino a quando non sei frustrato e abbastanza informato da trovare un modo migliore". In termini di codice, più volte viene eseguito un algoritmo inefficiente, più si ottengono prestazioni complessive ottimizzandolo. Esistono strumenti di profilazione che possono prendere un assembly binario e i suoi simboli di debug e mostrarti, dopo aver esaminato alcuni casi d'uso, quali righe di codice sono state eseguite di più. Quelle linee e quelle che corrono quelle linee sono ciò a cui dovresti prestare maggiore attenzione, perché ogni aumento di efficienza che raggiungi verrà moltiplicato.
  • Un algoritmo più complesso sembra un algoritmo meno complesso se si lancia hardware sufficiente . Ci sono alcune volte in cui devi solo capire che il tuo algoritmo si sta avvicinando ai limiti tecnici del sistema (o parte di esso) su cui lo stai eseguendo; da quel momento, se deve essere più veloce, otterrai di più semplicemente eseguendolo su un hardware migliore. Questo vale anche per la parallelizzazione; un algoritmo di complessità N 2 , quando eseguito su N core, appare lineare. Quindi, se sei sicuro di aver raggiunto il limite inferiore di complessità per il tipo di algoritmo che stai scrivendo, cerca modi per "dividere e conquistare".
  • È veloce quando è abbastanza veloce - A meno che tu non stia imballando a mano l'assemblaggio per colpire un determinato chip, c'è sempre qualcosa da guadagnare. Tuttavia, a meno che NON VUOI confezionare a mano un assemblaggio, devi sempre tenere a mente ciò che il cliente chiamerebbe "abbastanza buono". Ancora una volta, "l'ottimizzazione prematura è la radice di tutti i mali"; quando il tuo cliente lo chiama abbastanza velocemente, hai finito finché non pensa più che sia abbastanza veloce.

0

L'unica volta in cui preoccuparti dell'ottimizzazione è quando sai che hai a che fare con qualcosa di enorme o che sai che verrà eseguito un numero enorme di volte.

La definizione di "enorme" varia ovviamente in base a come sono i sistemi di destinazione.


0

Preferirei la versione a due righe semplicemente perché è più facile passare da un debugger. Una linea con più chiamate incorporate rende più difficile.

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.