Arquitetura de integração clr – desempenho

Aplica-se a:SQL ServerInstância Gerenciada de SQL do Azure

Este tópico discute algumas das opções de design que aprimoram o desempenho da integração do Microsoft SQL Server com o MICROSOFT .NET Framework CLR (Common Language Runtime).

O processo de compilação

Durante a compilação de expressões SQL, quando uma referência a uma rotina gerenciada é encontrada, um stub de MSIL (Linguagem Intermediária da Microsoft) é gerado. Esse stub inclui código para realizar marshaling dos parâmetros de rotina de SQL Server para o CLR, invocar a função e retornar o resultado. Este código de "cola" se baseia no tipo de parâmetro e na direção do parâmetro (de entrada, de saída ou de referência).

O código de associação permite otimizações específicas de tipo e garante a imposição eficiente de SQL Server semântica, como nulidade, restrição de facetas, por valor e manipulação de exceção padrão. Ao gerar código para os tipos exatos dos argumentos, você evita a coerção de tipos ou custos com a criação de objetos wrapper (o chamado "boxing") além do limite de invocação.

Em seguida, o stub gerado é compilado para código nativo e otimizado para a arquitetura de hardware específica na qual SQL Server é executado, usando os serviços de compilação JIT (just-in-time) do CLR. Os serviços JIT são invocados no nível do método e permitem que o ambiente de hospedagem SQL Server crie uma única unidade de compilação que abrange SQL Server e a execução de CLR. Depois que o stub é compilado, o ponteiro de função resultante se torna a implementação em tempo de execução da função. Essa abordagem da geração de código assegura que não haja custos adicionais com a invocação relacionados com a reflexão ou o acesso de metadados em tempo de execução.

Transições rápidas entre o SQL Server e o CLR

O processo de compilação gera um ponteiro de função que pode ser chamado em tempo de execução a partir do código nativo. No caso de funções definidas pelo usuário com valor escalar, essa invocação de função ocorre por linha. Para minimizar o custo de transição entre SQL Server e o CLR, as instruções que contêm qualquer invocação gerenciada têm uma etapa de inicialização para identificar o domínio do aplicativo de destino. Essa etapa de identificação reduz o custo de transição de cada linha.

Considerações sobre desempenho

O exemplo a seguir resume as considerações de desempenho específicas à integração clr em SQL Server. Informações mais detalhadas podem ser encontradas em "Usando a integração CLR no SQL Server 2005" no site do MSDN. Informações gerais sobre o desempenho do código gerenciado podem ser encontradas em "Melhorando o desempenho e a escalabilidade do aplicativo .NET" no site do MSDN.

Funções definidas pelo usuário

As funções CLR se beneficiam de um caminho de invocação mais rápido do que o das funções definidas pelo usuário do Transact-SQL. Além disso, o código gerenciado tem uma vantagem de desempenho decisiva em relação ao Transact-SQL em termos de código de procedimento, computação e manipulação de cadeia de caracteres. As funções CLR que utilizam muitos recursos de computação e que não executam acesso a dados são melhor escritas em código gerenciado. No entanto, as funções transact-SQL executam o acesso a dados com mais eficiência do que a integração CLR.

Agregações definidas pelo usuário

O código gerenciado pode ter um desempenho significativamente melhor que a agregação baseada em cursor. O código gerenciado geralmente executa funções de agregação de SQL Server internas um pouco mais lentas do que as internas. Se houver uma função de agregação interna nativa, é recomendável utilizá-la. Nos casos em que não há suporte nativo para a agregação necessária, por motivos de desempenho, considere uma agregação CLR definida pelo usuário como superior a uma implementação baseada em cursor.

Funções de streaming com valor de tabela

Frequentemente, os aplicativos precisam retornar uma tabela como resultado da invocação de uma função. Exemplos incluem a leitura de dados tabulares de um arquivo como parte de uma operação de importação e a conversão de valores separados por vírgula em uma representação relacional. Normalmente, isso pode ser feito materializando e preenchendo a tabela de resultados antes de ela poder ser consumida pelo chamador. A integração do CLR ao SQL Server introduz um novo mecanismo de extensibilidade chamado STVF (função com valor de tabela de streaming). As STVFs gerenciadas têm um desempenho melhor que o de implementações de procedimentos armazenados estendidos comparáveis.

STVFs são funções gerenciadas que retornam uma interface IEnumerable . IEnumerable tem métodos para navegar pelo conjunto de resultados retornado pelo STVF. Quando o STVF é invocado, o IEnumerable retornado é conectado diretamente ao plano de consulta. O plano de consulta chama métodos IEnumerable quando precisa buscar linhas. Esse modelo de iteração permite que os resultados sejam consumidos imediatamente depois que a primeira linha é gerada, em vez de aguardar até que toda a tabela seja preenchida. Ele também reduz significativamente a memória consumida ao invocar a função.

Matrizes vs. cursores

Quando os cursores Transact-SQL devem percorrer dados que são mais facilmente expressos como uma matriz, o código gerenciado pode ser usado com ganhos significativos de desempenho.

Dados de cadeia de caracteres

SQL Server dados de caractere, como varchar, podem ser do tipo SqlString ou SqlChars em funções gerenciadas. As variáveis SqlString criam uma instância do valor inteiro na memória. As variáveis SqlChars fornecem uma interface de streaming que pode ser usada para obter um melhor desempenho e escalabilidade por não criar uma instância do valor inteiro na memória. Isso se torna especialmente importante no caso de dados LOB (objeto grande). Além disso, os dados XML do servidor podem ser acessados por meio de uma interface de streaming retornada por SqlXml.CreateReader().

CLR vs. procedimentos armazenados estendidos

As APIs Microsoft.SqlServer.Server que permitem que procedimentos gerenciados enviem conjuntos de resultados de volta ao cliente têm um desempenho melhor que as APIs ODS (Open Data Services) usadas por procedimentos armazenados estendidos. Além disso, as APIs System.Data.SqlServer dão suporte a tipos de dados como xml, varchar(max), nvarchar(max)e varbinary(max), introduzidos no SQL Server 2005 (9.x), enquanto as APIs do ODS não foram estendidas para dar suporte aos novos tipos de dados.

Com o código gerenciado, SQL Server gerencia o uso de recursos como memória, threads e sincronização. Isso ocorre porque as APIs gerenciadas que expõem esses recursos são implementadas sobre o gerenciador de recursos do SQL Server. Por outro lado, SQL Server não tem exibição ou controle sobre o uso de recursos do procedimento armazenado estendido. Por exemplo, se um procedimento armazenado estendido consumir muitos recursos de CPU ou memória, não haverá como detectar ou controlar isso com SQL Server. Com o código gerenciado, no entanto, SQL Server pode detectar que um determinado thread não rendeu por um longo período de tempo e, em seguida, forçar a tarefa a produzir para que outro trabalho possa ser agendado. Consequentemente, o uso do código gerenciado fornece uma escalabilidade e um uso de recursos do sistema melhores.

O código gerenciado pode incorrer em uma sobrecarga adicional necessária para manter o ambiente de execução e executar verificações de segurança. Esse é o caso, por exemplo, ao executar dentro de SQL Server e várias transições de código gerenciado para nativo são necessárias (porque SQL Server precisa fazer manutenção adicional em configurações específicas de thread ao migrar para o código nativo e voltar). Consequentemente, os procedimentos armazenados estendidos podem superar significativamente o código gerenciado em execução dentro de SQL Server para casos em que há transições frequentes entre código gerenciado e nativo.

Observação

É recomendável não desenvolver novos procedimentos armazenados estendidos, pois esse recurso foi preterido.

Serialização nativa para tipos definidos pelo usuário

Os UDTs (tipos definidos pelo usuário) são criados como um mecanismo de extensibilidade para o sistema de tipo de escalar. SQL Server implementa um formato de serialização para UDTs chamado Format.Native. Durante a compilação, a estrutura do tipo é examinada para gerar MSIL personalizado para esta definição de tipo de particular.

A serialização nativa é a implementação padrão para SQL Server. A serialização definida pelo usuário invoca um método definido pelo autor do tipo para fazer a serialização. A serialização Format.Native deve ser usada quando possível para obter o melhor desempenho.

Normalização de UDTs comparáveis

As operações relacionais, como a classificação e a comparação de UDTs, funcionam diretamente na representação binária do valor. Isto é realizado armazenando uma representação normalizada (em ordem binária) do estado do UDT no disco.

A normalização tem duas vantagens: torna a operação de comparação consideravelmente menos cara por evitar a construção da instância de tipo e a sobrecarga de invocação do método, além de criar um domínio binário para o UDT, permitindo a construção de histogramas, índices e histogramas de valores do tipo. Consequentemente, os UDTs normalizados têm um perfil de desempenho muito semelhante ao dos tipos internos nativos para operações que não envolvem a invocação do método.

Uso da memória escalonável

Para que a coleta de lixo gerenciada seja executada e dimensionada bem em SQL Server, evite alocações grandes e simples. As alocações com mais de 88 quilobytes (KB) serão colocadas no heap de objetos grandes, que fará o desempenho e a escala da coleta de lixo serem muito piores que no caso de várias alocações menores. Por exemplo, se você precisar alocar uma matriz multidimensional grande, é melhor alocar uma matriz denteada (dispersa).

Consulte Também

Tipos definidos pelo usuário de CLR