Desempenho da integração CLR
Este tópico discute algumas das opções de design que aprimoram o desempenho da integração do Microsoft 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 é encontrada uma referência a uma rotina, é gerado um stub do MSIL (Microsoft Intermediate Language). Esse stub inclui código para realizar marshaling dos 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 do tipo e assegura a imposição eficiente da semântica do SQL Server, como a nulidade, a restrição de facetas, o tratamento de exceções por valor e 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 é então compilado em 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 de método e permitem que o ambiente de hospedagem do SQL Server crie uma única unidade de compilação que se estende pelas execuções 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 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. A fim de 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. Essa etapa de identificação reduz o custo de transição de cada linha.
Considerações sobre desempenho
Segue um resumo das considerações de desempenho específicas da integração CLR no 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 relativas ao desempenho de 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 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 sobre o Transact-SQL em termos de código de procedimento, computação e manipulação de cadeias 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. Entretanto, as funções Transact-SQL executam o acesso a dados de forma mais eficiente 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. Em geral, o desempenho do código gerenciado é um pouco mais lento que o das funções de agregação internas do SQL Server. 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
Freqüentemente, 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 de streaming com valor de tabela). 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 atravessar dados que são expressos mais facilmente como uma matriz, é possível usar código gerenciado com ganhos de desempenho significativos.
Dados de cadeia de caracteres
Em funções gerenciadas, os dados de caracteres do SQL Server, como varchar, podem ser do tipo SqlString ou SqlChars. 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, enquanto as APIs ODS não foram estendidas para dar suporte aos novos tipos de dados.
Com o código gerenciado, o SQL Server gerencia o uso de recursos como a memória, os threads e a sincronização. Isso se deve ao fato de as APIs gerenciadas que expõem esses recursos serem 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 consumir muitos recursos de CPU ou de memória, não há uma forma de detectá-lo ou controlá-lo com o SQL Server. Contudo, com o código gerenciado, o SQL Server pode detectar que um determinado thread não teve resultados por um longo período e então forçar a geração da tarefa, de forma que outros trabalhos possam ser agendados. Conseqüentemente, 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. Isso ocorre, por exemplo, ao executar no SQL Server quando várias transições do código gerenciado para o código nativo são necessárias (porque o SQL Server precisa executar uma manutenção adicional em configurações específicas de threads ao passar para o código nativo e voltar). Conseqüentemente, os procedimentos armazenados estendidos podem ter um desempenho significativamente melhor que o código gerenciado executado no SQL Server, nos casos em que há transições freqüentes entre o código gerenciado e o código 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. 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 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. Conseqüentemente, 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 coleta de lixo gerenciada seja executada e bem escalada no SQL Server, evite uma única alocação grande. 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).