Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Importante
Le tecniche descritte in questa sezione migliorano le prestazioni quando applicate ai percorsi ad accesso frequente nel codice. I percorsi ad accesso frequente sono le sezioni della codebase eseguite spesso e ripetutamente nelle normali operazioni. L'applicazione di queste tecniche al codice che non viene spesso eseguito avrà un impatto minimo. Prima di apportare modifiche per migliorare le prestazioni, è fondamentale misurare una baseline. Analizzare quindi la linea di base per determinare dove si verificano colli di bottiglia della memoria. È possibile ottenere informazioni su molti strumenti multipiattaforma per misurare le prestazioni dell'applicazione nella sezione Diagnostica e strumentazione. È possibile eseguire una sessione di profilatura nell'esercitazione per misurare l'utilizzo della memoria nella documentazione di Visual Studio.
Dopo aver misurato l'utilizzo della memoria e aver determinato che è possibile ridurre le allocazioni, usare le tecniche descritte in questa sezione per ridurre le allocazioni. Dopo ogni modifica successiva, misurare nuovamente l'utilizzo della memoria. Assicurarsi che ogni modifica abbia un impatto positivo sull'utilizzo della memoria nell'applicazione.
Il lavoro sulle prestazioni in .NET comporta spesso la rimozione delle allocazioni dal codice. Ogni blocco di memoria allocato deve essere liberato. Un numero minore di allocazioni riduce il tempo impiegato nella raccolta dei rifiuti. Consente tempi di esecuzione più prevedibili rimuovendo Garbage Collection da percorsi di codice specifici.
Una tattica comune per ridurre le allocazioni consiste nel modificare le strutture di dati critiche dai class
tipi ai struct
tipi. Questa modifica influisce sulla semantica dell'uso di tali tipi. I parametri e i valori restituiti vengono ora passati per valore anziché per riferimento. Il costo di copia di un valore è trascurabile se i tipi sono piccoli, tre parole o meno (considerando che una parola è di dimensioni naturali di un numero intero). È misurabile e può avere un impatto reale sulle prestazioni per i tipi più grandi. Per combattere l'effetto della copia, gli sviluppatori possono passare questi tipi passando per ref
per ottenere la semantica prevista.
Le funzionalità C# ref
offrono la possibilità di esprimere la semantica desiderata per struct
i tipi senza influire negativamente sull'usabilità complessiva. Prima di questi miglioramenti, gli sviluppatori devono ricorrere a unsafe
costrutti con puntatori e memoria non elaborata per ottenere lo stesso impatto sulle prestazioni. Il compilatore genera codice verificabile per le nuove ref
funzionalità correlate.
Il codice verificabile indica che il compilatore rileva possibili sovraccarichi del buffer o accede alla memoria non allocata o liberata. Il compilatore rileva e impedisce alcuni errori.
Passaggio e ritorno per riferimento
Le variabili in C# memorizzano valori. Nei struct
tipi, il valore è il contenuto di un'istanza del tipo. Nei class
tipi, il valore è un riferimento a un blocco di memoria che archivia un'istanza del tipo. L'aggiunta del ref
modificatore indica che la variabile archivia il riferimento al valore. Nei struct
tipi, il riferimento punta all'area di archiviazione contenente il valore. Nei class
tipi, il riferimento punta alla risorsa di archiviazione contenente il riferimento al blocco di memoria.
In C# i parametri dei metodi vengono passati per valore e i valori restituiti vengono restituiti per valore. Il valore dell'argomento viene passato al metodo . Il valore dell'argomento di ritorno è il valore restituito.
Il ref
, in
, ref readonly
o out
modificatore indica che l'argomento viene passato per riferimento. Un riferimento al percorso di archiviazione viene passato al metodo . L'aggiunta di ref
alla firma del metodo significa che il valore viene restituito tramite riferimento. Un riferimento alla posizione di archiviazione è il valore restituito.
È anche possibile usare l'assegnazione di riferimento per fare riferimento a un'altra variabile. Un'assegnazione tipica copia il valore del lato destro alla variabile sul lato sinistro dell'assegnazione.
Un'assegnazione di riferimento copia la posizione di memoria della variabile sul lato destro alla variabile sul lato sinistro. Ora ref
si riferisce alla variabile originale:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment
Console.WriteLine(location); // output: 42
sameLocation = 19; // assignment
Console.WriteLine(anInteger); // output: 19
Quando si assegna una variabile, si modifica il relativo valore. Quando si utilizza il comando ref assign su una variabile, si modifica a cosa essa fa riferimento.
È possibile lavorare direttamente con la memoria per i valori usando variabili ref
, passaggio per riferimento e assegnazione ref. Le regole di ambito applicate dal compilatore garantiscono la sicurezza quando si lavora direttamente con l'archiviazione.
Sia i modificatori ref readonly
che in
indicano che l'argomento deve essere passato per riferimento e non può essere riassegnato nel metodo. La differenza è che ref readonly
indica che il metodo usa il parametro come variabile. Il metodo potrebbe acquisire il parametro oppure restituire il parametro in base al riferimento di sola lettura. In questi casi, è consigliabile usare il ref readonly
modificatore. In caso contrario, il in
modificatore offre maggiore flessibilità. Non è necessario aggiungere il in
modificatore a un argomento per un in
parametro, quindi è possibile aggiornare le firme API esistenti in modo sicuro usando il in
modificatore. Il compilatore genera un avviso se non si aggiunge il modificatore ref
o in
a un argomento per un parametro ref readonly
.
Contesto sicuro di riferimento
C# include regole per le espressioni ref
per garantire che non sia possibile accedere a un'espressione ref
in cui l'archiviazione a cui fa riferimento non è più valida. Si consideri l'esempio seguente:
public ref int CantEscape()
{
int index = 42;
return ref index; // Error: index's ref safe context is the body of CantEscape
}
Il compilatore segnala un errore perché non è possibile restituire un riferimento a una variabile locale da un metodo. Il chiamante non può accedere alla risorsa di archiviazione a cui viene fatto riferimento. Il contesto di riferimento sicuro definisce l'ambito in cui un'espressione ref
è sicura per accedere o modificare. Nella tabella seguente sono elencati i contesti sicuri di riferimento per i tipi di variabili.
ref
i campi non possono essere dichiarati in un class
o un struct
non referenziato, quindi quelle righe non sono presenti nella tabella:
Dichiarazione | contesto di riferimento sicuro |
---|---|
locale non di riferimento | blocco in cui 'local' è dichiarato |
parametro non-ref | metodo corrente |
ref , ref readonly , in parametro |
metodo di chiamata |
parametro out |
metodo corrente |
class Campo |
metodo di chiamata |
campo non di riferimento struct |
metodo corrente |
ref campo di ref struct |
metodo di chiamata |
Una variabile può essere ref
restituita se il relativo contesto sicuro di riferimento è il metodo chiamante. Se il contesto di riferimento sicuro è il metodo corrente o un blocco, il ritorno non è consentito. Il frammento di codice seguente mostra due esempi. È possibile accedere a un campo membro dall'ambito che chiama un metodo, quindi il contesto di sicurezza di riferimento di un campo di classe o struct è il metodo chiamante. Il contesto di riferimento sicuro per un parametro con i modificatori ref
o in
è l'intero metodo. Entrambi possono essere ref
restituiti da un metodo membro:
private int anIndex;
public ref int RetrieveIndexRef()
{
return ref anIndex;
}
public ref int RefMin(ref int left, ref int right)
{
if (left < right)
return ref left;
else
return ref right;
}
Annotazioni
Quando il modificatore ref readonly
o in
viene applicato a un parametro, tale parametro può essere restituito da ref readonly
, non da ref
.
Il compilatore garantisce che un riferimento non possa uscire dal contesto sicuro di riferimento. È possibile usare ref
parametri, ref return
e ref
variabili locali in modo sicuro perché il compilatore rileva se è stato scritto accidentalmente codice in cui è possibile accedere a un'espressione ref
quando la risorsa di archiviazione non è valida.
Contesto sicuro e struct di riferimento
ref struct
I tipi richiedono più regole per assicurarsi che possano essere usate in modo sicuro. Un ref struct
tipo può includere ref
campi. Ciò richiede l'introduzione di un contesto sicuro. Per la maggior parte dei tipi, il contesto sicuro è il metodo chiamante. In altre parole, un valore che non è un ref struct
oggetto può sempre essere restituito da un metodo.
In modo informale, il contesto sicuro per un ref struct
è l'ambito in cui è possibile accedere a tutti i relativi ref
campi. In altre parole, è l'intersezione del contesto sicuro ref di tutti i relativi ref
campi. Il metodo seguente restituisce un oggetto ReadOnlySpan<char>
a un campo membro, pertanto il relativo contesto sicuro è il metodo :
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
Al contrario, il codice seguente genera un errore perché il ref field
membro dell'oggetto Span<int>
fa riferimento alla matrice allocata dello stack di interi. Il metodo non può sfuggire:
public Span<int> M()
{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}
Unificare i tipi di memoria
L'introduzione di System.Span<T> e System.Memory<T> fornisce un modello unificato per l'uso della memoria.
System.ReadOnlySpan<T> e System.ReadOnlyMemory<T> forniscono versioni di sola lettura per l'accesso alla memoria. Tutti forniscono un'astrazione su un blocco di memoria che archivia una matrice di elementi simili. La differenza è che Span<T>
e ReadOnlySpan<T>
sono ref struct
tipi, mentre Memory<T>
e ReadOnlyMemory<T>
sono struct
tipi. Gli intervalli contengono un elemento ref field
. Pertanto, le istanze di uno span non possono lasciare il contesto sicuro. Il contesto sicuro di un oggetto ref struct
è il contesto sicuro di riferimento del relativo ref field
. L'implementazione di Memory<T>
e ReadOnlyMemory<T>
rimuove questa restrizione. Questi tipi vengono usati per accedere direttamente ai buffer di memoria.
Migliorare le prestazioni con la sicurezza dei riferimenti
L'uso di queste funzionalità per migliorare le prestazioni comporta queste attività:
-
Evitare allocazioni: quando si modifica un tipo da a
class
astruct
, si modifica la modalità di archiviazione. Le variabili locali vengono archiviate nello stack. I membri vengono archiviati inline quando l'oggetto contenitore viene allocato. Questa modifica implica un minor numero di allocazioni e riduce il lavoro svolto dal Garbage Collector. Potrebbe anche diminuire la pressione della memoria, così che il collettore di garbage si attivi meno frequentemente. -
Mantieni la semantica dei riferimenti: la modifica di un tipo da a
class
modificastruct
la semantica del passaggio di una variabile a un metodo. Il codice che ha modificato lo stato dei parametri deve essere modificato. Ora che il parametro èstruct
, il metodo sta modificando una copia dell'oggetto originale. È possibile ripristinare la semantica originale passando tale parametro comeref
parametro. Dopo tale modifica, il metodo modifica nuovamente l'originalestruct
. -
Evitare di copiare dati: la copia di tipi più grandi
struct
può influire sulle prestazioni in alcuni percorsi di codice. È anche possibile aggiungere ilref
modificatore per passare strutture di dati di dimensioni maggiori ai metodi per riferimento anziché per valore. -
Limitare le modifiche: quando un
struct
tipo viene passato per riferimento, il metodo chiamato potrebbe modificare lo stato dello struct. È possibile sostituire ilref
modificatore con i modificatoriref readonly
oin
per indicare che l'argomento non può essere modificato. Preferisceref readonly
quando il metodo acquisisce il parametro o lo restituisce in base al riferimento di sola lettura. È anche possibile creare tipireadonly struct
o tipistruct
con membrireadonly
per fornire maggiore controllo sui membri di unstruct
che possono essere modificati. -
Manipolare direttamente la memoria: alcuni algoritmi sono più efficienti quando si considerano le strutture di dati come blocco di memoria contenente una sequenza di elementi. I
Span
tipi eMemory
forniscono accesso sicuro ai blocchi di memoria.
Nessuna di queste tecniche richiede unsafe
codice. Usato con saggezza, è possibile ottenere caratteristiche di prestazioni da codice sicuro che in precedenza era possibile solo usando tecniche non sicure. È possibile provare le tecniche manualmente nell'esercitazione sulla riduzione delle allocazioni di memoria.