Desempenho da arquitetura de integração CLR

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

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

O processo de compilação

Durante a compilação de expressões SQL, quando uma referência a uma rotina gerenciada é encontrada, um stub MSIL (Microsoft Intermediate Language) é gerado. Esse stub inclui código para empacotar os parâmetros de rotina do 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 cola permite otimizações específicas de tipo e garante a imposição eficiente da semântica do SQL Server, como nulidade, facetas restritivas, por valor e tratamento 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.

O stub gerado é compilado para código nativo e otimizado para a arquitetura de hardware específica na qual o 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 do SQL Server crie uma única unidade de compilação que abrange a execução do SQL Server e do 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 de geração de código garante que não haja custos extras de invocação relacionados à reflexão ou ao acesso a 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. Para 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 o 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 o desempenho

A seção a seguir resume as considerações de desempenho específicas da integração do CLR no SQL Server. Para obter mais informações, consulte Usando a integração CLR no SQL Server 2005. Para obter informações sobre o desempenho do código gerenciado, consulte Melhorando o desempenho e a escalabilidade do aplicativo .NET.

Funções definidas pelo usuário

As funções CLR se beneficiam de um caminho de invocação mais rápido do que as funções definidas pelo usuário do Transact-SQL. Além disso, o código gerenciado tem uma vantagem de desempenho decisiva sobre o Transact-SQL em termos de código de procedimento, computação e manipulação de cadeia de caracteres. As funções CLR que exigem muita computação e que não executam o 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 é executado um pouco mais lentamente do que as funções agregadas internas do SQL Server. Se houver uma função de agregação interna nativa, é recomendável utilizá-la. Nos casos em que a agregação necessária não tem suporte nativo, considere uma agregação CLR definida pelo usuário em uma implementação baseada em cursor por motivos de desempenho.

Funções com valor de tabela de streaming

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 no 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.

As STVFs são funções gerenciadas que retornam uma interface IEnumerable. A IEnumerable tem métodos para navegar pelo conjunto de resultados retornado pela STVF. Quando a STVF é invocada, a IEnumerable retornada é conectada diretamente ao plano de consulta. O plano de consulta chamará métodos de IEnumerable quando for necessário 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

Os dados de caracteres do SQL Server, 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 importante para dados de objetos grandes (LOB). 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 Microsoft.SqlServer.Server APIs (interfaces de programação de aplicativos) que permitem que os procedimentos gerenciados enviem conjuntos de resultados de volta ao cliente têm um desempenho melhor do que as APIs do Open Data Services (ODS) usadas por procedimentos armazenados estendidos. Além disso, as APIs System.Data.SqlServer oferecem suporte a tipos de dados como xml, varchar(max), nvarchar(max) e varbinary(max), introduzidos no SQL Server 2005 (9.x), enquanto as APIs ODS não foram estendidas para oferecer suporte aos novos tipos de dados.

Com o código gerenciado, o 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, o 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 há como detectar ou controlar isso com o SQL Server. Com o código gerenciado, no entanto, o SQL Server pode detectar que um determinado thread não foi gerado por um longo período de tempo e, em seguida, forçar a tarefa a ser renderizada para que outro trabalho possa ser agendado. Portanto, o uso de código gerenciado fornece melhor escalabilidade e uso de recursos do sistema.

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

Observação

Não desenvolva 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. O 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 do 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 dever ser usada quando possível para obter um 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 dois benefícios:

  • Isso torna a operação de comparação consideravelmente mais barata, evitando a construção da instância de tipo e a sobrecarga de invocação do método

  • ele cria um domínio binário para o UDT, permitindo a construção de histogramas, índices e histogramas para valores do tipo.

Portanto, os UDTs normalizados têm um perfil de desempenho semelhante aos tipos internos nativos para operações que não envolvem invocação de método.

Uso de memória escalável

Para que a coleta de lixo gerenciada seja bem executada e dimensionada no SQL Server, evite uma alocação grande e única. Alocações maiores que 88 KB (quilobytes) de tamanho são colocadas no Heap de Objeto Grande, o que faz com que a coleta de lixo seja executada e dimensionada pior do que muitas alocações menores. Por exemplo, se você precisar alocar uma matriz multidimensional grande, é melhor alocar uma matriz irregular (dispersa).