Prestazioni dell'architettura di integrazione CLR

Si applica a: SQL Server Istanza gestita di SQL di Azure

Questo articolo illustra alcune delle scelte di progettazione che migliorano le prestazioni dell'integrazione di SQL Server con Microsoft .NET Framework Common Language Runtime (CLR).

Processo di compilazione

Durante la compilazione di espressioni SQL, quando viene rilevato un riferimento a una routine gestita, viene generato uno stub MSIL (Microsoft Intermediate Language). Questo stub include il codice per effettuare il marshalling dei parametri di routine da SQL Server a CLR, richiamare la funzione e restituire il risultato. Questo codice di "unione" si basa sul tipo di parametro e sulla direzione del parametro (interna, esterna o di riferimento).

Il codice glue consente ottimizzazioni specifiche del tipo e garantisce un'applicazione efficiente della semantica di SQL Server, ad esempio nullbility, constraining facet, by-value e standard exception handling. La generazione di codice apposito per i tipi degli argomenti consente di evitare i costi legati all'assegnazione forzata o alla creazione di oggetti wrapper, chiamata "boxing", all'interno dei limiti della chiamata.

Lo stub generato viene quindi compilato in codice nativo e ottimizzato per l'architettura hardware specifica in cui viene eseguito SQL Server, usando i servizi di compilazione JIT (Just-In-Time) di CLR. I servizi JIT vengono richiamati a livello di metodo e consentono all'ambiente di hosting di SQL Server di creare una singola unità di compilazione che si estende sia sull'esecuzione di SQL Server che su CLR. Dopo aver compilato lo stub, il puntatore a funzione risultante diventa l'implementazione in fase di esecuzione della funzione. Questo approccio alla generazione di codice garantisce che non siano previsti costi aggiuntivi di chiamata correlati alla reflection o all'accesso ai metadati in fase di esecuzione.

Transizioni rapide tra SQL Server e CLR

Il processo di compilazione restituisce un puntatore a funzione che può essere chiamato in fase di esecuzione dal codice nativo. Per le funzioni definite dall'utente con valori scalari, questa chiamata di funzione avviene per riga. Per ridurre al minimo il costo della transizione tra SQL Server e CLR, le istruzioni che contengono qualsiasi chiamata gestita hanno un passaggio di avvio per identificare il dominio dell'applicazione di destinazione. Questo passaggio di identificazione riduce il costo della transizione per ogni riga.

Considerazioni sulle prestazioni

La sezione seguente riepiloga le considerazioni sulle prestazioni specifiche per l'integrazione con CLR in SQL Server. Per altre informazioni, vedere Uso dell'integrazione con CLR in SQL Server 2005. Per informazioni sulle prestazioni del codice gestito, vedere Miglioramento delle prestazioni e della scalabilità delle applicazioni .NET.

Funzioni definite dall'utente

Le funzioni CLR traggono vantaggio da un percorso di chiamata più rapido rispetto alle funzioni definite dall'utente Transact-SQL. Inoltre, il codice gestito offre un vantaggio decisivo sulle prestazioni rispetto a Transact-SQL in termini di codice procedurale, calcolo e manipolazione delle stringhe. Le funzioni CLR a elevato utilizzo di calcolo e che non eseguono l'accesso ai dati sono scritte meglio nel codice gestito. Le funzioni Transact-SQL, tuttavia, eseguono l'accesso ai dati in modo più efficiente rispetto all'integrazione con CLR.

Funzioni di aggregazione definite dall'utente

Il codice gestito può determinare prestazioni notevolmente superiori rispetto all'aggregazione basata sul cursore. Il codice gestito in genere funziona leggermente più lentamente rispetto alle funzioni di aggregazione predefinite di SQL Server. Se esiste una funzione di aggregazione predefinita nativa, è consigliabile utilizzarla. Nei casi in cui l'aggregazione necessaria non è supportata in modo nativo, considerare un'aggregazione CLR definita dall'utente su un'implementazione basata su cursore per motivi di prestazioni.

Funzioni con valori di tabella di streaming

Le applicazioni spesso devono restituire una tabella come risultato della chiamata di una funzione. Gli esempi includono la lettura di dati tabulari da un file nell'ambito di un'operazione di importazione e la conversione di valori delimitati da virgole in una rappresentazione relazionale. Per effettuare queste operazioni in genere è necessario materializzare e popolare la tabella dei risultati prima che possa essere utilizzata dal chiamante. L'integrazione di CLR in SQL Server introduce un nuovo meccanismo di estendibilità denominato funzione con valori di tabella di streaming (STVF). Le funzioni di flusso con valori di tabella offrono prestazioni migliori rispetto alle implementazioni delle stored procedure estese confrontabili.

Le funzioni di flusso con valori di tabella sono funzioni gestite che restituiscono un'interfaccia IEnumerable. IEnumerable dispone di metodi per spostarsi all'interno del set di risultati restituito da STVF. Quando viene richiamata la funzione, l'interfaccia IEnumerable restituita viene connessa direttamente al piano di query. Il piano di query chiama quindi i metodi IEnumerable qualora sia necessario recuperare righe. Questo modello di iterazione consente di utilizzare immediatamente i risultati subito dopo la produzione della prima riga, anziché dover attendere che venga popolata l'intera tabella. Riduce inoltre significativamente la quantità di memoria utilizzata quando si richiama la funzione.

Matrici e cursori

Quando i cursori Transact-SQL devono attraversare i dati che sono più facilmente espressi come matrice, il codice gestito può essere usato con miglioramenti significativi delle prestazioni.

Dati stringa

I dati di tipo carattere di SQL Server, ad esempio varchar, possono essere di tipo SqlString o SqlChars nelle funzioni gestite. Le variabili SqlString creano un'istanza dell'intero valore in memoria. Le variabili SqlChars forniscono un'interfaccia di flusso che può essere utilizzata per ottenere prestazioni migliori e una maggiore scalabilità creando un'istanza dell'intero valore in memoria. Questo aspetto diventa importante per i dati LOB (Large Object). È inoltre possibile accedere ai dati XML del server tramite un'interfaccia di flusso restituita da SqlXml.CreateReader().

CLR e stored procedure estese

Le Microsoft.SqlServer.Server API (Application Programming Interface) che consentono alle procedure gestite di inviare i set di risultati al client offrono prestazioni migliori rispetto alle API ODS (Open Data Services) usate dalle stored procedure estese. Inoltre, le API System.Data.SqlServer supportano tipi di dati come xml, varchar(max), nvarchar(max)e varbinary(max), introdotti in SQL Server 2005 (9.x), mentre le API ODS non sono state estese per supportare i nuovi tipi di dati.

Con il codice gestito, SQL Server gestisce l'uso di risorse come memoria, thread e sincronizzazione. Ciò avviene perché le API gestite che espongono queste risorse vengono implementate in gestione risorse di SQL Server. Al contrario, SQL Server non ha alcuna visualizzazione o controllo sull'utilizzo delle risorse della stored procedure estesa. Ad esempio, se una stored procedure estesa utilizza una quantità eccessiva di CPU o risorse di memoria, non è possibile rilevare o controllare questa operazione con SQL Server. Con il codice gestito, tuttavia, SQL Server può rilevare che un determinato thread non è stato restituito per un lungo periodo di tempo e quindi forzare la resa dell'attività in modo che altri lavori possano essere pianificati. Pertanto, l'uso del codice gestito offre una migliore scalabilità e un utilizzo delle risorse di sistema.

Il codice gestito potrebbe comportare un sovraccarico aggiuntivo necessario per gestire l'ambiente di esecuzione ed eseguire controlli di sicurezza. Questo è il caso, ad esempio, quando si esegue all'interno di SQL Server e sono necessarie numerose transizioni dal codice gestito al codice nativo (poiché SQL Server deve eseguire una manutenzione aggiuntiva sulle impostazioni specifiche del thread quando si passa al codice nativo e vice-back). Pertanto, le stored procedure estese possono migliorare notevolmente il codice gestito in esecuzione all'interno di SQL Server per i casi in cui sono presenti frequenti transizioni tra codice gestito e nativo.

Nota

Non sviluppare nuove stored procedure estese, perché questa funzionalità è deprecata.

Serializzazione nativa per i tipi definiti dall'utente

I tipi definiti dall'utente sono progettati come un meccanismo di extensibility per il sistema di tipo scalare. SQL Server implementa un formato di serializzazione per i tipi definiti dall'utente denominati Format.Native. Durante la compilazione, la struttura del tipo viene esaminata per generare un codice MSIL personalizzato per la definizione del tipo specifico.

La serializzazione nativa è l'implementazione predefinita per SQL Server. La serializzazione definita dall'utente richiama un metodo definito dall'autore del tipo per eseguire la serializzazione. La serializzazione Format.Native deve essere utilizzata quando possibile per ottenere prestazioni ottimali.

Normalizzazione di tipi definiti dall'utente confrontabili

Le operazioni relazionali, ad esempio l'ordinamento e il confronto dei tipi definiti dall'utente, agiscono direttamente sulla rappresentazione binaria del valore. A tale scopo, è necessario archiviare una rappresentazione normalizzata (con ordinamento binario) dello stato del tipo definito dall'utente su disco.

La normalizzazione offre due vantaggi:

  • rende l'operazione di confronto notevolmente meno costosa evitando la costruzione dell'istanza del tipo e il sovraccarico di chiamata al metodo

  • crea un dominio binario per il tipo definito dall'utente, consentendo la costruzione di istogrammi, indici e istogrammi per i valori del tipo.

Pertanto, i tipi definiti dall'utente normalizzati hanno un profilo di prestazioni simile ai tipi predefiniti nativi per le operazioni che non comportano la chiamata al metodo.

Utilizzo scalabile della memoria

Per consentire l'esecuzione e la scalabilità ottimale di Garbage Collection gestita in SQL Server, evitare allocazioni singole e di grandi dimensioni. Le allocazioni superiori a 88 kilobyte (KB) vengono posizionate nell'heap di oggetti di grandi dimensioni, causando l'esecuzione e la scalabilità di Garbage Collection peggiore rispetto a molte allocazioni più piccole. Ad esempio, se è necessario allocare una matrice multidimensionale di grandi dimensioni, è preferibile allocare una matrice irregolare (a dispersione).