Novità del runtime di .NET 11

Questo articolo descrive le nuove funzionalità del runtime di .NET per .NET 11. L'ultimo aggiornamento è stato eseguito per l'anteprima 7.

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>

Un net11.0 progetto non richiede <EnablePreviewFeatures>true</EnablePreviewFeatures> più l'uso di Runtime Async.

Le librerie di runtime .NET stesse vengono compilate con runtime-async=on. Le librerie di runtime non contengono più macchine a stati generate dal compilatore e si basano interamente sull'asincrono fornito dal runtime. In questo modo è possibile eseguire la migrazione di un'intera app (con solo dipendenze di libreria) al nuovo modello e offre una convalida generale delle prestazioni e funzionale della funzionalità. Il team di prodotto accoglie con favore qualsiasi segnalazione, positiva o negativa, sulle variazioni del throughput e delle dimensioni della libreria che osservate.

.NET 11 include anche due miglioramenti aggiuntivi:

  • Override covarianti TaskTask<T>: Quando una classe derivata restituisce Task<T> per un metodo di base che restituisce Task, il runtime genera ora un thunk con valore restituito void che colma la differenza nella convenzione di chiamata, in modo che il dispatch virtuale funzioni per entrambe le varianti. La stessa correzione si applica a NativeAOT.
  • Inlining in crossgen2: Sono state rimosse restrizioni che impedivano l'inlining dei metodi asincroni di runtime durante la compilazione ReadyToRun (R2R). Tutti i test asincroni superano sia con crossgen2 sia con R2R composito, e l'inlining delle chiamate asincrone prive di await (il percorso rapido sincrono) è confermato da capo a capo.

Annotazioni

Le DOTNET_RuntimeAsync variabili di ambiente e UNSUPPORTED_RuntimeAsync che in precedenza controllavano il comportamento asincrono di runtime sono state rimosse. Per rifiutare esplicitamente runtime-async per ogni progetto, impostare <UseRuntimeAsync>false</UseRuntimeAsync> nel file di progetto invece di basarsi sulla variabile di ambiente.

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

Runtime Async supporta 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 delle prestazioni di Runtime Async

Runtime Async include diversi miglioramenti delle prestazioni:

  • Supporto async JIT: Il JIT compila una versione dedicata al runtime asincrono di un metodo sincrono che restituisce un Task, anziché delegare tramite un thunk. Il JIT trasforma le chiamate in coda del metodo in chiamate asincrone di runtime e attende il task che altrimenti verrebbe restituito, eliminando un ulteriore livello di indirezione.
  • Punti di sospensione con merge della coda: Il JIT esegue il merge dei punti di sospensione asincroni, così da ridurre le dimensioni del codice generato.
  • Continuazioni in cache: Le continuazioni usate per i thunk di attività richiamabili asincrone in fase di esecuzione sono memorizzate nella cache e riutilizzate.
  • I metodi aggregati sono esclusi: I metodi già aggregati sono esclusi da runtime-async, evitando operazioni ridondanti.
  • Compilazione asincrona a livelli: Le versioni asincrone ora rientrano nella compilazione a livelli, quindi i metodi asincroni usati più di frequente ottengono le ottimizzazioni di livello 1 dopo la fase di warm-up.
  • Intrinseche delle factory Task e ValueTask: Il JIT riconosce i metodi factory comuni, ad esempio Task.FromResult, Task.CompletedTask e ValueTask.FromResult, e li ottimizza in percorsi asincroni più rapidi.
  • Ottimizzazioni tail-await: Le tail call implicite nei metodi asincroni vengono nuovamente abilitate quando il metodo restituisce direttamente un altro risultato asincrono e await Task.Yield() evitano allocazioni aggiuntive nei percorsi asincroni del runtime.

Continuazioni asincrone senza ExecutionContext

Le continuazioni asincrone possono ora disattivare l'acquisizione e il ripristino di ExecutionContext. ExecutionContext trasporta il contesto ambientale, come i valori AsyncLocal<T>, attraverso i punti await. Ogni continuazione Task acquisiva in precedenza un'istantanea del contesto e la ripristinava prima di essere eseguita, anche quando non veniva usato alcuno stato AsyncLocal<T> e il ripristino non aveva alcun effetto.

Il runtime ora rileva quando una continuazione non ha nulla da ripristinare e ignora completamente il ciclo di acquisizione/ripristino. Task, Task<T>, ValueTaske ValueTask<T> tutti traggono vantaggio da questa modifica, così come il percorso di implementazione asincrona di runtime. Le applicazioni che usano ConfigureAwait(false) e AsyncLocal<T> con parsimonia registrano un sovraccarico ridotto nei percorsi di codice asincrono con throughput elevato.

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.
  • 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.
  • Math.BigMul su x64:Math.BigMul(long, long, out long) è ora significativamente più veloce su x64. JIT genera una singola MUL r/m64 istruzione quando entrambi gli operandi sono valori a 64 bit e il chiamante richiede la metà elevata del risultato, eliminando la precedente chiamata helper.
  • Rimossa la limitazione del prologo a un singolo IG: Il JIT non richiede più che il prologo della funzione rientri in un singolo gruppo di istruzioni (IG). Prologhi complessi con molti registri salvati, allocazioni di stack di grandi dimensioni o configurazione dello stato asincrono di runtime non attivano più percorsi di fallback.
  • SELECT(cond, cns, cns) riduzione: Il JIT ora riduce le selezioni condizionali in cui entrambi i rami producono la stessa costante a quella costante; ad esempio, condition ? 42 : 42 diventa 42. Questa ottimizzazione elimina i confronti non necessari che possono comparire dopo che ottimizzazioni precedenti hanno unificato i rami.
  • Intrinseche AVX-VNNI-512:System.Runtime.Intrinsics.X86.AvxVnni.V512 espone forme a 512 bit delle intrinseche di moltiplicazione-accumulo su CPU con supporto AVX512-VNNI.
  • Conversioni saturanti da virgola mobile: Le conversioni double e float non controllate verso tipi interi di dimensioni ridotte ora vengono limitate ai valori minimi e massimi del tipo invece di andare in overflow a causa del troncamento intermedio.
  • ARM64 Vector<T> per riferimento per SVE: quando il runtime viene compilato con il supporto ARM SVE, Vector<T> i valori vengono passati per riferimento anziché per valore, in linea con la convenzione di chiamata ARM per i tipi scalabili e consentendo una migliore generazione del codice per il codice con uso intensivo di SVE.

Per migliorare le prestazioni e la qualità del codice, .NET 11 aggiunge altre ottimizzazioni JIT:

Sequenza di riduzione costanteEqual

Il JIT può ora ottimizzare una chiamata a string.Equals o ReadOnlySpan<T>.SequenceEqual i cui operandi sono entrambi costanti in fase di compilazione, sostituendo il confronto byte per byte con il risultato costante true o false. Questo è particolarmente importante dopo l'inlining, quando una funzione chiamante passa un altro valore letterale a una funzione di supporto che confronta il valore con una stringa nota. Quando IsAdmin viene espansa inline in un chiamante che passa "Guest", il JIT vede "Guest" == "Admin" e lo riduce a false:

static bool IsAdmin(string role) => role == "Admin";

L'ottimizzazione si applica ai valori letterali di stringa, ai campi const string e ai valori letterali UTF-8 (ad esempio, "PNG"u8).

Miglioramenti alla devirtualizzazione

JIT può ora devirtualizzare metodi virtuali generici, inclusi metodi virtuali generici non condivisi e metodi virtuali generici condivisi che non richiedono una ricerca in fase di esecuzione. Anche i metodi di interfaccia predefiniti dichiarati nelle interfacce generiche possono essere devirtualizzati.

Inoltre, il risultato di Activator.CreateInstance<T>() viene ora considerato come con un tipo esatto, consentendo all'JIT di devirtualizzare le chiamate successive effettuate sull'oggetto restituito.

Questi miglioramenti consentono di sbloccare altre ottimizzazioni downstream, ad esempio l'inlining e la riduzione costante, riducendo il sovraccarico di invio del runtime e migliorando le prestazioni. Per esempio:

IfaceGeneric<string> caller = new Caller<string>();
Iface obj = Activator.CreateInstance<Impl>();
caller.DefaultImpl(obj, "test"); // Can be optimized down to Console.WriteLine("test")

interface Iface
{
    void GenericMethod<T>(T value);
}

interface IfaceGeneric<T>
{
    void DefaultImpl(Iface obj, T value) => obj.GenericMethod(value);
}

class Impl : Iface
{
    public void GenericMethod<T>(T value) => Console.WriteLine(value);
}

class Caller<T> : IfaceGeneric<T>
{
}

Qui il JIT può determinare l'implementazione esatta alla base di entrambe le chiamate all'interfaccia, devirtualizzarle, eseguirne l'inlining lungo la catena di chiamate e infine ridurre il codice fino all'equivalente di Console.WriteLine("test").

Eliminazione dei controlli dei limiti dopo una protezione a intervalli vuoti

JIT ora preleva l'asserzione length != 0 da un controllo di intervallo vuoto e la usa per dimostrare che il controllo dei limiti sul primo elemento ha esito positivo:

if (!span.IsEmpty && span[0] == value)
{
    // The bounds check on span[0] is now eliminated.
}

Eliminazione di diramazioni ridondanti e dei test

Quando un predicato esterno è già implicato da un ramo interno, il JIT ora rimuove il controllo esterno ridondante. Analogamente, quando il codice assegna un valore in un condizionale e quindi lo testa immediatamente, il secondo test viene eliminato:

if (x > 0)
{
    if (x > 1) S();  // The outer x > 0 check is folded away.
}

int y = condition ? 1 : 2;
if (y == 1) A(); else B();  // The y == 1 test is eliminated;
                             // each branch of the ternary goes directly to A() or B().

Queste ottimizzazioni sono più visibili dopo l'inlining, quando i controlli provenienti da metodi diversi confluiscono nello stesso codice compilato.

Intrinseci hardware e generazione di codice

.NET 11 include diversi nuovi miglioramenti alla generazione di codice e intrinseci hardware:

  • Accelerazione F16C per Halffloat ↔ le conversioni in x64: quando la CPU supporta F16C (la maggior parte dell'hardware con supporto per AVX2), le conversioni tra Half efloat/doubleora usano le istruzioni dedicate vcvtph2ps/vcvtps2ph anziché le chiamate helper.
  • Migliore modellazione dei costi per SIMD x86/x64: I costi di esecuzione e dimensioni a virgola mobile di JIT rispecchiavano in precedenza i presupposti dell'era x87. I costi aggiornati, che riflettono il moderno hardware SSE/AVX, consentono al JIT di prendere decisioni migliori sul sollevamento del codice e sull'eliminazione delle sottoespressioni comuni (CSE) nel codice SIMD.
  • Più veloce DotProduct su AVX: la fase di lowering per operazioni di tipo Vector128.Dot ora genera una sequenza mul + permute + add anziché vdpps/vdppd quando AVX è disponibile, che risulta sistematicamente più veloce.
  • Più veloce IndexOfAnyAsciiSearcher su Arm64: le versioni Arm64 di Vector*.Count, IndexOf e LastIndexOf non passano più attraverso ExtractMostSignificantBits, con un miglioramento del 5–50% nei carichi di lavoro che usano queste API nel loro ciclo principale.
  • Arm64 ToScalar per numeri interi a 64 bit:ToScalar in Vector*<long> e Vector*<ulong> ora usa fmov invece di umov, che è più breve e veloce nella maggior parte dei core.
  • SVE CreateWhile Espansione dell'API:CreateWhile ottiene varianti firmate, doublee single insieme a quelle non firmate esistenti, completando la superficie di generazione del predicato per i cicli SVE.
  • Nuovo rilevamento dei set di istruzioni SVE2 e Arm64: Il runtime ora segnala SVE_AES, SVE_SHA3, SVE_SM4, SHA3 e SM4 come set di istruzioni separati, consentendo query tramite Sve2*.IsSupported per tali funzionalità sull'hardware compatibile.
  • Correzione del rilevamento di AVX10: La cache dei bit CPUID per AVX10 veniva sovrascritta da una query successiva, il che poteva far sì che AVX10 venisse segnalato in modo errato su hardware compatibile. Questo problema è stato risolto.

Miglioramenti di ReadyToRun

Comparer<T>.Default e EqualityComparer<T>.Default sono ora specializzati in immagini ReadyToRun (R2R). In precedenza, i comparer predefiniti usavano la riflessione che R2R non poteva vedere in anticipo, costringendo i chiamanti a ricorrere al JIT. R2R genera ora un helper specializzato nell'immagine, rispecchiando l'approccio NativeAOT, e i benchmark mostrano un miglioramento fino a 20 volte per le operazioni sulle raccolte che si basano sul comparatore predefinito.

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

Il supporto di Browser e WebAssembly offre 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à.

.NET include miglioramenti per CoreCLR-on-WebAssembly:

  • WebCIL V1 è l'impostazione predefinita per le compilazioni WASM CoreCLR: L'intestazione WebCIL condivisa ottiene un TableBase campo (28 → 32 byte). Sia i lettori Mono che CoreCLR accettano V0 e V1. Crossgen2 WasmObjectWriter genera direttamente V1 e le build dell'SDK WASM basate su CoreCLR impostano per impostazione predefinita WasmWebcilVersion su V1.
  • Il re-link nativo funziona per le app CORECLR WASM: Una pipeline completa basata su Emscripten sostituisce le destinazioni stub precedenti. Il nuovo collegamento dotnet.native.wasm dal runtime pack e l'inclusione di codice nativo personalizzato tramite NativeFileReference ora funziona.
  • Minimizzazione di JavaScript nelle build Release: Le build Browser CoreCLR Release includono JavaScript minimizzato.
  • La pubblicazione NativeAOT per WASM non elimina più gli assembly satellite dei pacchetti: Gli assembly satellite dei pacchetti NuGet vengono ora passati a ILC e rimossi dall'output di pubblicazione, correggendo la localizzazione per le app pubblicate con AOT che dipendono da pacchetti come System.CommandLine.

CoreCLR in WebAssembly esegue ora la suite di test delle librerie end-to-end.

Supporto della piattaforma per più di 1024 CPU

Il runtime .NET può ora inizializzare nei computer con più di 1024 processori logici. In precedenza, sched_getaffinity è stato chiamato con il valore predefinito cpu_set_t (con un limite di 1024), causando l'errore di inizializzazione nei server con numero di core elevato. Il runtime alloca ora il set di CPU in modo dinamico. Il GC conserva il suo limite dell’heap di 1024, ma il limite del numero di CPU viene rimosso.

Registrazione delle segnalazioni di arresto anomalo nel processo

Un nuovo meccanismo di segnalazione degli arresti anomali nel processo acquisisce informazioni diagnostiche dall'interno del processo che sta subendo l'arresto anomalo prima della sua terminazione. In precedenza, i dati diagnostici sugli arresti anomali venivano raccolti da un monitor esterno al processo. Anche se l'approccio esterno al processo è sicuro, può perdere informazioni disponibili solo all'interno del processo in fase di terminazione. Il nuovo percorso in-process registra la traccia dello stack gestito, l'elenco dei moduli e lo stato chiave del runtime in un percorso noto prima che il processo termini.

Questa funzionalità è specifica per le piattaforme mobili.

NativeAOT: invocazione dell'interfaccia più veloce

NativeAOT ora usa un helper di dispatch per le chiamate ai metodi di interfaccia. Invece di una sequenza di chiamata diretta di fat-pointer, il runtime instrada l'invio dell'interfaccia tramite un helper condiviso che può essere patchato all'implementazione corretta dopo il riscaldamento del sito di chiamata. Ciò riduce le dimensioni binarie dei siti di chiamata di interfaccia e migliora la velocità effettiva nei carichi di lavoro con molte chiamate al metodo di interfaccia.

L'invio di metodi virtuali generici usa anche l'infrastruttura di cella dispatch condivisa, che migliora le prestazioni delle chiamate monomorfiche e polimorfiche. Inoltre, Assembly.GetCallingAssembly() ora funziona in NativeAOT quando sono disponibili i dati di analisi dello stack.

Costruzione e composizione della corsia SIMD

System.Runtime.Intrinsics include ora le API di costruzione e composizione della corsia per i tipi di vettore hardware. Le nuove API consentono di costruire un vettore da corsie specificate singolarmente e estrarre o riordinare le corsie tra vettori. Ciò consente un controllo preciso e portabile sul posizionamento degli elementi vettoriali SIMD senza tornare agli intrinseci specifici della piattaforma.

I nuovi metodi rientrano in alcune famiglie:

  • Costruzione con pattern:CreateGeometricSequence, CreateAlternatingSequencee CreateHarmonicSequence compila un vettore da un valore iniziale e una regola.
  • Interleave e de-interleave:Zip, ZipLower/ZipUpper, Unzip, UnzipEven/UnzipOdd.
  • Riorganizzare: Famiglia Concat (ConcatLowerLower, ConcatLowerUpper, ConcatUpperLower, ConcatUpperUpper) e Reverse.
using System.Runtime.Intrinsics;

// {1, 2, 4, 8} — each lane is the previous lane times two
Vector128<int> powers = Vector128.CreateGeometricSequence(1, 2);

// Interleave two vectors lane-by-lane
(Vector128<int> lower, Vector128<int> upper) =
    Vector128.Zip(Vector128.Create(1), Vector128.Create(2));

Queste API sono disponibili in Vector128<T>, Vector256<T>Vector512<T>, , Vector64<T>e Vector<T>. Si tratta di blocchi predefiniti per l'elaborazione di immagini, l'elaborazione del segnale digitale audio e altri carichi di lavoro a elevato utilizzo di SIMD che richiedono un controllo granulare sul layout degli elementi vettoriali.

Diagnostica di esecuzione

Il profiler asincrono ora esegue la strumentazione sia dei metodi asincroni del runtime sia dei metodi della macchina a stati asincrona generati dal compilatore, quindi gli strumenti ricevono un modello di eventi coerente indipendentemente dallo stile di implementazione asincrona. Il runtime aggiunge anche un supporto migliorato per la registrazione delle mappe delle prestazioni dell'interprete e corregge l'analisi degli CORECLR_NOTIFICATION_PROFILERS elenchi che non terminano con un punto e virgola.

Vedere anche