2009-05-27 6 views
18

Per qualche motivo, ho sempre pensato che ai campi readonly fosse associato un sovraccarico, che ho pensato mentre il CLR tiene traccia del fatto che un campo readonly è stato inizializzato o meno. Il sovraccarico qui sarebbe un po 'di memoria extra per tenere traccia dello stato e un controllo quando si assegna un valore.Esiste un sovraccarico di runtime in sola lettura?

Forse ho assunto questo perché non sapevo un campo readonly poteva essere inizializzato solo all'interno di un costruttore o all'interno della dichiarazione campo stesso e senza un controllo in fase di esecuzione, non sarebbe in grado di garantire che non è essere assegnato a più volte in vari modi. Ma ora lo so, potrebbe facilmente essere controllato staticamente dal compilatore C#, giusto? Quindi è così?

Un altro motivo è che ho letto che l'utilizzo di readonly ha un impatto "lieve" sulle prestazioni, ma non sono mai entrati in questa affermazione e non riesco a trovare informazioni su questo argomento, quindi la mia domanda. Non so quale altro impatto sulle prestazioni potrebbe esserci a parte i controlli di runtime.

Una terza ragione è che ho visto che readonly è conservato nel IL compilato come initonly, quindi qual è la ragione di queste informazioni per essere in IL, se readonly non è altro che una garanzia da parte del compilatore C# che il campo non viene mai assegnato all'esterno di un costruttore o dichiarazione?

D'altra parte, ho scoperto che è possibile impostare il valore di un readonly int tramite riflessione senza il CLR generando un'eccezione, che non dovrebbe essere possibile se readonly era un controllo di runtime.

Quindi la mia ipotesi è: il 'readonlyness' è solo una funzione di compilazione del tempo, qualcuno può confermare/negare questo? E se lo è, qual è la ragione per cui queste informazioni devono essere incluse nell'IL?

risposta

16

Devi guardarlo dallo stesso punto di vista dei modificatori di accesso. I modificatori di accesso esistono in IL, ma sono davvero un controllo in fase di esecuzione? (1) Non posso assegnare direttamente campi privati ​​in fase di compilazione, (2) posso assegnarli usando la reflection. Finora sembra che non ci sia un controllo in fase di esecuzione, come in sola lettura.

Ma esaminiamo i modificatori di accesso. Procedere come segue:

  1. Crea assieme A.dll con public class C
  2. Crea un b.exe Assemblea che fa riferimento a A.dll. B.exe utilizza la classe C.
  3. Costruisci i due assiemi. L'esecuzione di B.exe funziona bene.
  4. Ricostruire A.dll ma impostare la classe C su interna. Sostituire A.dll nella directory di B.exe.

Ora, l'esecuzione di B.exe genera un'eccezione di runtime.

I modificatori di accesso esistono anche in IL, giusto? Quindi qual è il loro scopo? Lo scopo è che gli altri assembly che fanno riferimento a un assembly .Net devono sapere a cosa sono autorizzati ad accedere e a cosa non possono accedere, sia in fase di compilazione che in fase di esecuzione.

Readonly sembra avere uno scopo simile in IL. Indica agli altri gruppi se possono scrivere in un campo su un particolare tipo. Tuttavia, a sola letturanon sembra che abbia lo stesso controllo di runtime che i modificatori di accesso mostrano nel mio esempio sopra.Sembra che readonly sia un controllo in fase di compilazione e non si verifichi in fase di esecuzione. Guarda un esempio di prestazioni qui: Read-only performance vs const.

Ancora, questo non significa che l'IL è inutile. L'IL si assicura che si verifichi un errore in fase di compilazione in primo luogo. Ricorda, quando costruisci non costruisci contro il codice, ma gli assembly.

+0

Whoops, hai trovato la stessa analisi delle prestazioni che ho fatto. Quindi +1 a te e ho cancellato la mia risposta (meno dettagliata). – Eddie

+0

Ha perfettamente senso, grazie :) – JulianR

6

Se si utilizza una variabile di istanza standard, in sola lettura verrà eseguita quasi identicamente a una variabile normale. L'IL aggiunto diventa un controllo del tempo di compilazione, ma è praticamente ignorato in fase di runtime.

Se stai usando un membro statico di sola lettura, le cose sono un po 'diverso ...

Dal momento che l'elemento statico di sola lettura è impostato durante il costruttore statico, il JIT "sa" che un valore esiste. Non c'è memoria extra - solo di sola lettura impedisce ad altri metodi di impostarlo, ma è un controllo del tempo di compilazione.

SIA il JIT sa che questo membro non può mai cambiare, diventa "hardcoded" in fase di esecuzione, quindi l'effetto finale è come avere un valore const. La differenza è che ci vorrà più tempo durante il JIT stesso, dal momento che il compilatore JIT ha bisogno di fare del lavoro extra per cablare il valore di readonly in posizione. (Questo sarà molto veloce, però.)

Expert C++/CLI di Marcus Hegee ha una spiegazione abbastanza buona di questo.

3

Anche se in sola lettura aveva effetto solo in fase di compilazione, sarebbe comunque necessario memorizzare i dati nell'assieme (ad esempio IL). Il CLR è un comuneLingua Runtime: le classi scritte in una lingua possono essere utilizzate ed estese anche da altre lingue.

Poiché ogni compilatore per CLR non saprà leggere e compilare ogni altra lingua, al fine di preservare la semantica dei campi readonly, tali dati devono essere memorizzati nell'assembly in modo che i compilatori per altre lingue lo rispetterò.

Ovviamente, il fatto che il campo sia contrassegnato con readonly significa che il JIT può fare altre cose, come l'ottimizzazione (ad esempio l'uso in linea del valore), ecc. Indipendentemente dal fatto che si è utilizzato il riflesso per modificare il valore del campo, la creazione di IL che modifica un campo initonly al di fuori del costruttore corrispondente (istanza o statico, a seconda del tipo di campo), comporterà un assembly non verificabile.

4

Un punto importante non ancora menzionato da altre risposte è che quando si accede a un campo di sola lettura o quando si accede a qualsiasi proprietà, la richiesta viene soddisfatta utilizzando una copia dei dati. Se i dati in questione sono di tipo valore con più di 4-8 byte di dati, il costo di questa copia aggiuntiva può talvolta essere significativo. Si noti che mentre il costo delle strutture aumenta di 16 byte a 17, le strutture possono essere un po 'più grandi e ancora più veloci delle classi in molte applicazioni, se non vengono copiate troppo spesso. Ad esempio, se si suppone che si abbia un tipo che rappresenta i vertici di un triangolo nello spazio tridimensionale. Un'implementazione diretta sarebbe una struct contenente una struct con tre float per ogni punto; probabilmente 36 byte totali. Se i punti e le coordinate all'interno di ciascun punto sono campi pubblici mutabili, è possibile accedere a someTriangle.P1.X rapidamente e facilmente, senza dover copiare alcun dato tranne la coordinata Y del vertice 1. D'altra parte, se P1 era una proprietà o un readonly campo, il compilatore dovrebbe copiare P1 in una struttura temporanea e quindi leggere X da quello.

+0

Questa potrebbe essere una domanda molto vecchia, ma nel caso in cui un futuro lettore sia interessato, il seguente post di Jon Skeet contiene informazioni molto più dettagliate relative a questa risposta: http: //codeblog.jonskeet .uk/2014/07/16/micro-ottimizzazione-the-sorprendente-inefficiency-of-readonly-fields/ –

+0

@KlitosKyriacou: Non penso di aver visto quel particolare post prima. Desidero davvero che .NET abbia fornito un mezzo con cui i metodi di struttura potrebbero promettere di non scrivere "questo" o dire esplicitamente che lo fanno, poiché ciò renderebbe possibile (1) migliorare le prestazioni in molti casi quando si chiamano metodi che non scrivi 'this' [come indicato nel blog], e (2) permetti a uno di avere metodi sicuri che scrivano' this' e far sì che il compilatore rifiuti i tentativi di invocarli su variabili diverse. Ci sono un certo numero di casi in cui il tipo di dati più logico semanticamente sarebbe una struttura mutabile, dal momento che ... – supercat

+0

... il modello 'structVar = structVar.withSomeChange();' non può essere reso thread-safe, ma 'structVar. makeSomeChange() 'può [usando' Interlocked.CompareExchange']. Sfortunatamente, se una classe espone una proprietà di [ipotetico] tipo di struttura 'AtomicBitVector32', un compilatore produrrà silenziosamente il codice fasullo per un'operazione come' thing.flags.TestAndSetBit (23); 'if' Flags' è una proprietà piuttosto che una campo. – supercat