2010-01-29 2 views
26

Si prega di ignorare la leggibilità del codice in questa domanda.Il compilatore C# è abbastanza intelligente da ottimizzare questo codice?

In termini di prestazioni, qualora il seguente codice essere scritta in questo modo:

int maxResults = criteria.MaxResults; 

if (maxResults > 0) 
{ 
    while (accounts.Count > maxResults) 
     accounts.RemoveAt(maxResults); 
}

o come questo:

if (criteria.MaxResults > 0) 
{ 
    while (accounts.Count > criteria.MaxResults) 
     accounts.RemoveAt(criteria.MaxResults); 
}

?

Edit: criteria è un class, e MaxResults è una struttura intero semplice (vale a dire, public int MaxResults { get { return _maxResults; } }

Vuol il compilatore C# ossequio MaxResults come una scatola nera e valutare ogni volta O è intelligente abbastanza da figura.? Ho ricevuto 3 chiamate alla stessa proprietà senza alcuna modifica di quella proprietà tra le chiamate?

Una delle leggi dell'ottimizzazione è il calcolo preliminare, quindi ho istintivamente scritto questo codice come primo elenco, ma sono curioso di sapere se questo genere di cose viene fatto per me a automaticamente (ancora, ignorare la leggibilità del codice).

(Nota: non sono interessato all'argomento "micro-ottimizzazione", che potrebbe essere valido nel caso specifico che ho postato. Vorrei solo una teoria dietro ciò che sta succedendo o non succedendo .)

+1

Perché ti preoccupi anche del se all'esterno? se gli account sono array o raccolte, il conteggio non sarà mai inferiore a 0. E se non è mai inferiore a 0 a chi importa se MaxResults è inferiore a 0? (non verrà mai ripetuto) –

+4

@Matthew: Si prega di non rimanere appesi alla logica, poiché (a) è scritto correttamente per la mia applicazione, e (b) non è quello che sto chiedendo in questa domanda. –

+1

account.Take (maxResults) –

risposta

58

Prima di tutto, l'unico modo per rispondere effettivamente alle domande relative alle prestazioni è provare in entrambi i modi e testare i risultati in condizioni realistiche.

Detto questo, le altre risposte che dicono che "il compilatore" non esegue questa ottimizzazione perché la proprietà potrebbe avere effetti collaterali sia giusti che sbagliati. Il problema con la domanda (a parte il problema fondamentale a cui semplicemente non si può rispondere senza effettivamente provarlo e misurare il risultato) è che "il compilatore" è in realtà due compilatori: il compilatore C#, che compila a MSIL, e il compilatore JIT , che compila IL per il codice macchina.

Il compilatore C# non esegue mai questo tipo di ottimizzazione; come indicato, fare ciò richiederebbe che il compilatore esegua il peer nel codice che viene chiamato e verifica che il risultato che calcola non cambi durante la durata del codice del callee. Il compilatore C# non lo fa.

Il compilatore JIT potrebbe. Nessuna ragione per cui non potesse. Ha tutto il codice seduto proprio lì. È completamente gratuito per allineare il getter della proprietà e, se il jitter determina che il getter di proprietà inline restituisce un valore che può essere memorizzato nella cache in un registro e riutilizzato, allora è libero di farlo. (Se non si vuole fare così perché il valore potrebbe essere modificato su un altro thread allora hai già una condizione di competizione bug, risolvere il bug prima di preoccuparsi di prestazioni.)

Sia il jitter in realtà fa in linea la proprietà recuperare e quindi registrare il valore, non ne ho idea. Non conosco praticamente nulla del jitter. Ma è permesso di farlo se lo ritiene opportuno. Se sei curioso di sapere se lo faccia o no, puoi (1) chiedere a qualcuno che è nella squadra che ha scritto il jitter, o (2) esaminare il codice jitted nel debugger.

Infine, vorrei cogliere l'occasione per notare che i risultati di calcolo una volta, la memorizzazione del risultato e il riutilizzo è non sempre un'ottimizzazione. Questa è una domanda sorprendentemente complicata. Ci sono tutti i tipi di cose da ottimizzare per:

  • tempo di esecuzione

  • dimensione del codice eseguibile - questo ha un effetto importante sul tempo eseguibile perché grande codice richiede più tempo per caricare, aumenta la dimensione del working set , mette sotto pressione le cache del processore, la RAM e il file di paging. Piccolo codice lento è spesso a lungo termine più veloce di un grande codice veloce in metriche importanti come il tempo di avvio e la localizzazione della cache.

  • allocazione registro: questo ha anche un effetto importante sui tempi di esecuzione, in particolare in architetture come x86 che hanno un numero limitato di registri disponibili. La registrazione di un valore per il riutilizzo rapido può significare che ci sono meno registri disponibili per altre operazioni che necessitano di ottimizzazione; forse l'ottimizzazione di quelle operazioni sarebbe una vittoria netta.

  • e così via. Diventa davvero complicato molto velocemente.

In breve, non si può assolutamente sapere se la scrittura del codice per memorizzare nella cache il risultato, piuttosto che ricalcolare in realtà è (1) più veloce, o meglio (2) performante. Prestazioni migliori non sempre significano rendere più veloce l'esecuzione di una particolare routine. Una migliore prestazione consiste nel capire quali risorse sono importanti per l'utente - tempo di esecuzione, memoria, working set, tempo di avvio e così via - e l'ottimizzazione per queste cose. Non puoi farlo senza (1) parlare con i tuoi clienti per scoprire cosa gli interessa e (2) misurare effettivamente per vedere se i tuoi cambiamenti stanno avendo un effetto misurabile nella direzione desiderata.

+2

+1 Speravo di attirare la tua attenzione su questa domanda. Grazie per l'ottima risposta. –

+1

Grande +1 per aver sottolineato che "il compilatore" non è un termine significativo in .Net (o Java, se è per questo, o qualsiasi altro ambiente che compila in un formato intermedio e quindi applica [può applicare] JIT). –

+6

@Eric: le tue risposte sono sempre fantastiche. Senza nemmeno vederti nominare in fondo a questa risposta, sapevo che doveva essere te. Grazie a condividere i dettagli del funzionamento interno di .Net e C# saranno tutti noi mortali. –

7

Se MaxResults è una struttura allora no, non sarà ottimizzarlo, perché il getter può avere una logica complessa, dicono:

private int _maxResults; 
public int MaxReuslts { 
    get { return _maxResults++; } 
    set { _maxResults = value; } 
} 

vedere come il comportamento cambierebbe se in linee il codice ?

Se non c'è logica ... entrambi i metodi che hai scritto va bene, si tratta di una differenza molto minuto e tutto su come leggibile sia a voi (o la vostra squadra) ... Sei tu quello a guardarla .

+1

Beh, anche se fosse una proprietà potrebbe essere in linea ma ciò sarebbe fatto dal jit. Quel valore potrebbe anche essere cambiato da un altro thread, quindi potrebbe non essere sicuro ottimizzarlo. –

+0

@Vinzz: Grazie! –

2

perché non testarlo?

è sufficiente configurare 2 app per console che fanno sembrare 10 milioni di volte e confrontare i risultati ... ricordati di eseguirli come app correttamente installate che sono state installate correttamente altrimenti non puoi garantire che non stai semplicemente eseguendo il msil.

In realtà probabilmente riceverete circa 5 risposte dicendo "non dovreste preoccuparvi dell'ottimizzazione". chiaramente non scrivono routine che devono essere il più veloci possibile prima di essere leggibili (ad es. giochi).

Se questa parte di codice fa parte di un ciclo che viene eseguito miliardi di volte, questa ottimizzazione potrebbe essere utile. Ad esempio, i risultati massimi potrebbero essere un metodo sottoposto a override e quindi potrebbe essere necessario discutere delle chiamate ai metodi virtuali.

Davvero il SOLO modo di rispondere a una di queste domande è di capire che questo è un pezzo di codice che beneficerà dell'ottimizzazione. Quindi è necessario conoscere il tipo di cose che aumentano il tempo di esecuzione. Davvero noi comuni mortali non possiamo farlo a priori e quindi dobbiamo semplicemente provare 2-3 diverse versioni del codice e poi testarlo.

+0

boom e abbastanza sicuro ho anticipato il commento di leggibilità ... ha persino usato il grassetto; p –

0

Se criteria è un tipo di classe, dubito che sarebbe ottimizzato, perché un altro thread potrebbe sempre modificare quel valore nel frattempo. Per struct s non sono sicuro, ma il mio istinto è che non sarà ottimizzato, ma penso che non farebbe comunque molta differenza nelle prestazioni in quel caso.

+0

Non c'è motivo per cui una struttura non possa essere modificata in un altro thread più o meno nello stesso modo in cui una classe può. le strutture non sono garantite come immutabili e possono essere passate per riferimento. – Thorarin

+0

Questo è molto vero. Le strutture mutevoli sono così malvagie, non le uso mai. Così mi sono quasi dimenticato di quel caso. Quindi il mio istinto diventa finalmente una vera saggezza. ;-) – herzmeister

4

Sarà chiamato e valutato ogni volta. Il compilatore non ha modo di determinare se un metodo (o getter) è deterministico e puro (senza effetti collaterali).

Si noti che la valutazione effettiva della proprietà può essere evidenziata dal compilatore JIT, rendendola efficace come un campo semplice.

È buona prassi rendere la valutazione della proprietà un'operazione economica. Se si esegue un calcolo pesante nel getter, prendere in considerazione la memorizzazione manuale del risultato nella cache o la modifica in un metodo.

6

I tuoi due esempi di codice sono garantiti per avere lo stesso risultato in ambienti a thread singolo, che .Net non lo è, e se MaxResults è un campo (non una proprietà). Il compilatore non può assumere, a meno che non si utilizzino le funzioni di sincronizzazione, che criteria.MaxResults non cambierà durante il ciclo. Se si tratta di una proprietà, non può presumere che l'utilizzo della proprietà non abbia effetti collaterali.

Eric Lippert sottolinea abbastanza correttamente che dipende molto da cosa intendi con "il compilatore". Il compilatore C# -> IL? O il compilatore IL -> codice macchina (JIT)? E ha ragione di sottolineare che il JIT potrebbe essere in grado di ottimizzare il getter della proprietà, dato che ha tutte le informazioni (mentre il compilatore C# -> IL non lo fa, necessariamente). Non cambierà la situazione con più thread, ma è comunque un buon punto.