Partilhar via


Desempenho da arquitetura de integração CLR

Aplica-se a:SQL ServerAzure SQL Managed Instance

Este artigo discute algumas das opções de design que melhoram o desempenho da integração do SQL Server com o CLR (Common Language Runtime) do .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 de linguagem intermediária comum (CIL) é 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. Esta cola código é baseado no tipo de parâmetro e na direção do parâmetro (em, foraou referência).

O código de cola permite otimizações específicas do tipo e garante a imposição eficiente da semântica do SQL Server, como anulabilidade, facetas restritivas, valor por valor e tratamento de exceções padrão. Ao gerar código para os tipos exatos dos argumentos, você evita a coerção de tipo ou os custos de criação de objetos wrapper (chamados de "boxe") através do limite de invocação.

O stub gerado é então 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 just-in-time (JIT) 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. Uma vez que o stub é compilado, o ponteiro da função resultante torna-se 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 adicionais 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 produz 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 acontece por linha. Para minimizar o custo da 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. Esta etapa de identificação reduz o custo de transição para cada linha.

Considerações sobre desempenho

A seção a seguir resume as considerações de desempenho específicas da integração 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 Improving .NET Application Performance and Scalability.

Funções definidas pelo utilizador

As funções CLR se beneficiam de um caminho de invocação mais rápido do que Transact-SQL funções definidas pelo usuário. Além disso, o código gerenciado tem uma vantagem de desempenho decisiva sobre Transact-SQL em termos de código de procedimento, computação e manipulação de cadeia de caracteres. As funções CLR que são intensivas em computação e que não executam acesso a dados são melhor escritas em código gerenciado. Transact-SQL funções, no entanto, executam o acesso aos dados de forma mais eficiente do que a integração CLR.

Agregações definidas pelo utilizador

O código gerenciado pode superar significativamente a agregação baseada em cursor. O código gerenciado geralmente executa um pouco mais lento do que as funções de agregação internas do SQL Server. Recomendamos que, se existir uma função agregada interna nativa, você deve usá-la. Nos casos em que a agregação necessária não é suportada nativamente, considere uma agregação definida pelo usuário CLR em uma implementação baseada em cursor por motivos de desempenho.

Streaming de funções com valor de tabela

Os aplicativos geralmente precisam retornar uma tabela como resultado da invocação de uma função. Os 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írgulas em uma representação relacional. Normalmente, você pode fazer isso materializando e preenchendo a tabela de resultados antes que ela possa 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). Os STVFs gerenciados têm um desempenho melhor do que implementações comparáveis de procedimentos armazenados estendidos.

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

Matrizes vs. cursores

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

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. SqlString variáveis criam uma instância de todo o valor na memória. SqlChars variáveis fornecem uma interface de streaming que pode ser usada para obter melhor desempenho e escalabilidade, não criando uma instância de todo o valor na memória. Isso se torna importante para dados de objeto grande (LOB). Além disso, os dados XML do servidor podem ser acessados por meio de uma interface de streaming retornada pelo SqlXml.CreateReader().

CLR vs. procedimentos armazenados estendidos

As interfaces de programação de aplicativos (APIs) Microsoft.SqlServer.Server que permitem que procedimentos gerenciados enviem conjuntos de resultados de volta ao cliente têm um desempenho melhor do que as APIs ODS (Open Data Services) usadas por procedimentos armazenados estendidos. Além disso, as APIs System.Data.SqlServer suportam tipos de dados como xml, varchar(max), nvarchar(max)e varbinary(max), enquanto as APIs ODS não foram estendidas para suportar esses tipos de dados.

Com 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 nenhuma exibição ou controle sobre o uso de recursos do procedimento armazenado estendido. Por exemplo, se um procedimento armazenado estendido consome muitos recursos de CPU ou memória, não há como detetar ou controlar isso com o SQL Server. Com o código gerenciado, no entanto, o SQL Server pode detetar que um determinado thread não rendeu por um longo período de tempo e, em seguida, forçar a tarefa a render para que outro trabalho possa ser agendado. Assim, 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, quando executado 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 em configurações específicas de thread ao migrar para código nativo e vice-versa). Portanto, os procedimentos armazenados estendidos podem superar significativamente o código gerenciado em execução dentro do SQL Server para casos em que há transições frequentes entre código gerenciado e nativo.

Observação

Não desenvolva novos procedimentos armazenados estendidos, porque esse recurso foi preterido.

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

Os tipos definidos pelo usuário (UDTs) são projetados como um mecanismo de extensibilidade para o sistema de tipo 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 CIL que é personalizado para essa definição de tipo específico.

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

Normalização de UDTs comparáveis

Operações relacionais, como classificar e comparar UDTs, operam diretamente na representação binária do valor. Isso é feito armazenando uma representação normalizada (ordenada binária) do estado do UDT no disco.

A normalização tem dois benefícios:

  • Isso torna a operação de comparação consideravelmente menos dispendiosa, 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, as UDTs normalizadas 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 tenha um bom desempenho e seja dimensionada no SQL Server, evite uma alocação grande e única. Alocações maiores que 88 kilobytes (KB) de tamanho são colocadas no Heap de Objeto Grande, o que faz com que a coleta de lixo tenha um desempenho e escale pior do que muitas alocações menores. Por exemplo, se você precisar alocar uma grande matriz multidimensional, é melhor alocar uma matriz irregular (dispersa).