Novità del runtime di .NET 11

Questo articolo descrive le nuove funzionalità del runtime di .NET per .NET 11. È stato aggiornato per l'ultima volta per l'anteprima 3.

Requisiti hardware minimi aggiornati

I requisiti hardware minimi per .NET 11 sono stati aggiornati per richiedere set di istruzioni più moderni nelle architetture x86/x64 e Arm64. Inoltre, le destinazioni di compilazione ReadyToRun (R2R) sono state aggiornate per sfruttare i vantaggi delle funzionalità hardware più recenti.

Requisiti di Arm64

Per quanto riguarda Apple, non viene apportata alcuna modifica all'hardware minimo o al target ReadyToRun. Il chip Apple M1 è approssimativamente equivalente a armv8.5-a e fornisce supporto per almeno i set di istruzioni AdvSimd (NEON), CRC, DOTPROD, LSE, RCPC, RCPC2 e RDMA.

Per Linux, non viene apportata alcuna modifica all'hardware minimo. .NET continua a supportare dispositivi come Raspberry Pi che potrebbero fornire supporto solo per il set di istruzioni AdvSimd. La destinazione ReadyToRun è stata aggiornata per includere il LSE set di istruzioni, che potrebbe comportare un sovraccarico aggiuntivo se si avvia un'applicazione.

Per il sistema operativo Windows, il baseline viene aggiornato per richiedere il set di istruzioni LSE. Si tratta di richiesto da Windows 11 e da tutte le CPU Arm64 supportate ufficialmente da Windows 10. Inoltre, è in linea con i requisiti arm SBSA (Server Base System Architecture). La destinazione ReadyToRun è stata aggiornata in modo che sia armv8.2-a + RCPC, che fornisce supporto per almeno AdvSimd, CRC, LSERCPC, e RDMAe copre la maggior parte dell'hardware ufficialmente supportato.

Sistema operativo Valore minimo JIT/AOT precedente Nuovo valore minimo JIT/AOT Destinazione R2R precedente Nuova destinazione R2R
Mela Apple M1 Nessuna modifica Apple M1 Nessuna modifica
Linux armv8.0-a Nessuna modifica armv8.0-a armv8.0-a + LSE
Windows armv8.0-a armv8.0-a + LSE armv8.0-a armv8.2-a + RCPC

Requisiti x86/x64

Per tutti e tre i sistemi operativi (Apple, Linux e Windows), la baseline viene aggiornata da x86-64-v1 a x86-64-v2. In questo modo, l'hardware non garantisce più solo CMOV, CX8, SSE e SSE2, ma garantisce anche CX16, POPCNT, SSE3, SSSE3, SSE4.1 e SSE4.2. Questa garanzia è richiesta da Windows 11 e da tutte le CPU x86/x64 ufficialmente supportate in Windows 10. Include tutti i chip ancora ufficialmente supportati da Intel e AMD, con gli ultimi chip meno recenti che hanno esaurito il supporto intorno al 2013.

La destinazione ReadyToRun è stata aggiornata a x86-64-v3 per Windows e Linux, che include inoltre i set di istruzioni AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT e MOVBE. La destinazione ReadyToRun per Apple rimane invariata.

Sistema operativo Valore minimo JIT/AOT precedente Nuovo valore minimo JIT/AOT Destinazione R2R precedente Nuova destinazione R2R
Mela x86-64-v1 x86-64-v2 x86-64-v2 Nessuna modifica
Linux x86-64-v1 x86-64-v2 x86-64-v2 x86-64-v3
Windows x86-64-v1 x86-64-v2 x86-64-v2 x86-64-v3

Impatto

A partire da .NET 11, .NET non riesce a essere eseguito su hardware meno recente e potrebbe stampare un messaggio simile al seguente:

La CPU corrente manca uno o più set di istruzioni di base.

Per gli assembly con supporto per ReadyToRun, potrebbe verificarsi un sovraccarico di avvio aggiuntivo su alcuni hardware supportati che non soddisfano il supporto previsto per un dispositivo tipico.

Motivo della modifica

.NET supporta un'ampia gamma di hardware, spesso oltre i requisiti hardware minimi applicati dal sistema operativo sottostante. Questo supporto aggiunge una notevole complessità alla codebase, in particolare per hardware molto meno recente che è improbabile che sia ancora in uso. Definisce inoltre un "minor comune denominatore" a cui devono adeguarsi le destinazioni AOT per impostazione predefinita, il che, in alcuni scenari, può comportare una riduzione delle prestazioni.

L'aggiornamento alla baseline minima è stato apportato per ridurre la complessità di manutenzione della codebase e per allinearsi meglio ai requisiti hardware documentati (e spesso applicati) del sistema operativo sottostante.

Per altre informazioni, vedere Requisiti hardware minimi aggiornati.

Runtime Async

.NET 11 introduce l'asincrono nativo del runtime (Runtime Async V2), un passo significativo verso la sostituzione delle macchine di stato async generate dal compilatore con sospensione e ripresa gestite dal runtime. Invece che il compilatore genera classi di macchine a stati, il runtime stesso tiene traccia dell'esecuzione asincrona, producendo tracce dello stack più pulite, una migliore debugbilità e un sovraccarico inferiore.

Runtime Async è una funzionalità di anteprima. Per acconsentire esplicitamente, aggiungere la proprietà seguente al file di progetto:

<PropertyGroup>
  <Features>runtime-async=on</Features>
</PropertyGroup>

A partire dall'anteprima 3, un net11.0 progetto non richiede <EnablePreviewFeatures>true</EnablePreviewFeatures> più l'uso di Runtime Async.

Tracce dello stack live più pulite

Il miglioramento più visibile riguarda le analisi dello stack in tempo reale, ovvero ciò che i profiler, i debugger e new StackTrace() visualizzano durante l'esecuzione. Con l'asincrona generata dal compilatore, ogni metodo asincrono produce più frame dall'infrastruttura della macchina a stati. Con Runtime Async, i metodi effettivi vengono visualizzati direttamente nello stack di chiamate.

// To enable runtime async, add the following to your .csproj:
//   <Features>runtime-async=on</Features>

await OuterAsync();

static async Task OuterAsync()
{
    await Task.CompletedTask;
    await MiddleAsync();
}

static async Task MiddleAsync()
{
    await Task.CompletedTask;
    await InnerAsync();
}

static async Task InnerAsync()
{
    await Task.CompletedTask;
    Console.WriteLine(new StackTrace(fNeedFileInfo: true));
}

Senza runtime-async—13 fotogrammi, infrastruttura macchina a stati visibile:

   at Program.<<Main>$>g__InnerAsync|0_2() in Program.cs:line 24
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__InnerAsync|0_2()
   at Program.<<Main>$>g__MiddleAsync|0_1() in Program.cs:line 14
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__MiddleAsync|0_1()
   at Program.<<Main>$>g__OuterAsync|0_0() in Program.cs:line 8
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__OuterAsync|0_0()
   at Program.<Main>$(String[] args) in Program.cs:line 3
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<Main>$(String[] args)
   at Program.<Main>(String[] args)

Con runtime-async—5 frame, la vera catena di chiamate:

   at Program.<<Main>$>g__InnerAsync|0_2() in Program.cs:line 24
   at Program.<<Main>$>g__MiddleAsync|0_1() in Program.cs:line 14
   at Program.<<Main>$>g__OuterAsync|0_0() in Program.cs:line 8
   at Program.<Main>$(String[] args) in Program.cs:line 3
   at Program.<Main>(String[] args)

Annotazioni

Le tracce dello stack di eccezioni (da catch (Exception ex)) hanno già lo stesso aspetto con o senza Runtime Async, perché l'operazione di pulizia esistente di ExceptionDispatchInfo nel codice generato dal compilatore gestisce il caso. Il miglioramento è in ciò che vedi durante l'esecuzione in tempo reale.

Questo miglioramento beneficia tutto ciò che ispeziona lo stack di esecuzione in tempo reale, inclusi gli strumenti di profiling, la registrazione diagnostica e la finestra dello stack di chiamate del debugger.

Supporto di NativeAOT e ReadyToRun

Anteprima 3 aggiunge il supporto di Runtime Async per la compilazione NativeAOT e ReadyToRun. Ciò estende la funzionalità oltre al codice compilato jit agli scenari compilati in anticipo. Il runtime riutilizza anche gli oggetti di continuazione con maggiore aggressività ed evita di salvare le variabili locali invariate, riducendo la pressione di allocazione nel codice con un uso intenso di async.

Miglioramenti nel debugging

I breakpoints ora si collegano correttamente all'interno dei metodi runtime asincroni e il debugger può attraversare i confini await senza entrare nell'infrastruttura generata dal compilatore.

Miglioramenti JIT

  • Eliminazione dei limiti: Il compilatore JIT elimina ora i controlli dei limiti per il modello comune in cui un indice più una costante viene confrontata con una lunghezza, ad esempio i + cns < len. Elimina anche controlli dei limiti più ridondanti per l'accesso dall'indice finale (ad esempio, values[^1]). Questi miglioramenti riducono i controlli ridondanti nei cicli stretti e migliorano il throughput per le operazioni su array e span.
  • Rimozione del contesto controllato ridondante: Il JIT può ora dimostrare e rimuovere contesti aritmetici con controlli ridondanti, ad esempio quando si sa già che un valore rientra nell'intervallo. Questa ottimizzazione elimina i controlli di overflow non necessari nel codice generato.
  • Riduzione dell'espressione switch: Le espressioni a switch più destinazioni ora si riducono in controlli più semplici e senza biforcazioni quando le destinazioni sono un piccolo set di costanti, ad esempio x is 0 or 1 or 2 or 3 or 4.
  • Conversioni da uint a float/double più veloci: La conversione da uint a float o double è più veloce sull'hardware x86 pre-AVX-512.
  • Devirtualizzazione nelle immagini ReadyToRun: Le immagini ReadyToRun (R2R) adesso possono devirtualizzare le chiamate a metodi virtuali generici non condivisi, migliorando le prestazioni del codice compilato in anticipo per scenari generici.
  • Intrinseche SVE2: Sono disponibili nuove funzioni intrinseche Arm SVE2 (Scalable Vector Extension 2): ShiftRightLogicalNarrowingSaturate(Even|Odd). Questi espandono il set di operazioni vettorializzate disponibili nell'hardware arm che supporta SVE2.

Miglioramenti delle macchine virtuali

  • Invio dell'interfaccia memorizzata nella cache su piattaforme non JIT: Nelle piattaforme che non supportano JIT, ad esempio iOS, l'invio dell'interfaccia è stato sottoposto a un costoso percorso di correzione generico. L'invio memorizzato nella cache produce fino a 200 volte miglioramenti nel codice a elevato utilizzo di interfaccia su queste destinazioni.
  • Guid.NewGuid() su Linux:Guid.NewGuid() su Linux ora usa la chiamata di sistema (syscall) con la memorizzazione nella cache batch anziché leggere da getrandom(); producendo un miglioramento della velocità effettiva di circa il 12% per la generazione di GUID.

Miglioramenti di WebAssembly

L'anteprima 3 espande il supporto di browser e WebAssembly con diversi miglioramenti:

  • Caricamento del payload WebCIL: Il runtime può ora caricare direttamente i payload WebCIL, migliorando la compatibilità con gli scenari di distribuzione basati su browser.
  • Criteri di debug: La qualità di traccia dello stack e dei simboli per il debug di WebAssembly è stata migliorata, semplificando la diagnosi dei problemi nelle app di .NET ospitate nel browser.
  • float[], Span<float> e ArraySegment<float> marshalling:float[], Span<float> e ArraySegment<float> sono ora sottoposti a marshalling più direttamente attraverso i confini di JavaScript, riducendo il sovraccarico per il codice ad alta interoperabilità.

Vedere anche