segunda-feira, 1 de dezembro de 2008

.Net: Testes de Performance e Melhores Práticas - Parte 1

Iniciando nosso bloco "Testes de Performance e Melhores Práticas" vamos apresentar a utilização do DataGridView e do SqlDataReader em alguns pontos.
Pra quem já conhece bem os procedimentos, fique ligado nos tópicos e vá direto ao assunto sem perder seu tempo. Quem ainda esta começando, montamos um cenário, caso queira reproduzir.

1º Teste do dia:
Vamos executar alguns testes de inserção de linhas em um DataGrid e verificar sua perforamance.

No exemplo utilizaremos:
Um DataGridView que iremos chamar de Grid.
O Grid possui 3 Colunas dentro dele. Seus nomes são: Coluna01, Coluna02 e Coluna03.
Estão colunas possuem indíces, que representam sua atual posição no Grid. São respectivamente 0,1 e 2.
O IndiceDaLinha representa o número da linha atual, que estamos inserindo no grid.

Primeiro, vamos fazer o Grid inserir 20.000 (vinte mil) linhas nas suas 3 colunas utilizando indíce numérico para definir a coluna que receberá o valor. Desta forma, o comando fica +- assim:
Grid.Rows(IndiceDaLinha).Cells(0).Value = "ValorNaColuna1"
Notem que o índice em negrito está representando a coluna de nome Coluna01, e estou definindo para esta linha e coluna do Grid, o valor texto "ValorNaColuna1", apenas para teste. Vamos inserir um valor parecido para as demais colunas.
Grid.Rows(IndiceDaLinha).Cells(1).Value = "ValorNaColuna2"
Grid.Rows(IndiceDaLinha).Cells(2).Value = "ValorNaColuna3"

No segundo teste, utilizamos o mesmo Grid, a mesma quantidade de linhas e colunas com seus respectivos nomes, mas desta vez ao invés de utilizarmos o índice da coluna (0,1,2) vamos utilizar o nome da coluna. O comando fica +- assim:
Grid.Rows(IndiceDaLinha).Cells("Coluna01").Value = "ValorNaColuna1"
Notem que agora passamos o nome da coluna entre "" para
Grid.Rows(IndiceDaLinha).Cells("Coluna02").Value = "ValorNaColuna2"
Grid.Rows(IndiceDaLinha).Cells("Coluna03").Value = "ValorNaColuna3"

Resultado: A resposta de tudo isso, foi uma melhor performance em 0,97 segundos para inserir todas as 20.000 (vinte mil) linhas, se usarmos por índices numéricos (exemplo 01). Algo parecido com 0,0000485 segundos por linha.

Opinião pessoal: para uma aplicação comun, do dia a dia, utilizar um código elegante ajuda os desenvolvedores e não piora tanto a situação. Quem convive diariamente com relatórios de 20.000 linhas? Este 1 segundo é tão importante para gerar o relatório? Por isso, eu fico com a utilização do índice pelo nome da coluna. Mas isso é só opinião minha, nada demais! ;D

Foi sem querer, mas valeu: Até deixar os dois ambientes iguais, eu verifiquei um terceiro teste de performance!
Ao solicitar a ultima linha do datagrid, eu selecionei assim "me.Grid.Rows.GetLastRow(Displayed)"
No outro grid, sem querer eu coloquei assim "me.Grid.Rows.GetLastRow(None)"
A diferença? Com NONE, eu consegui mais de 5 SEGUNDOS mais rapido do que Displayed! O motivo exato eu ainda não sei, mas os dois métodos trouxeram as linhas de forma correta e consegui manipular também. Ou seja, 5 segundos mais rapido e nenhum problema "aparente" (não fui a fundo para descobrir, não era esse o mérito) Mais tarde faço outros testes e dou uma pesquisada, dai sim posto para vocês o motivo disso.

2º Teste do dia:
No segundo teste, vamos executar uma consulta de banco de dados em um SqlDataReader. Após isso, vamos verificar quais pontos podem fazer efeito na melhoria de performance da aplicação.

Assim como DataGridView, podemos buscar dados de um DataReader pelo índice da coluna no DataReader ou pelo nome da Coluna.
O índice da coluna no reader, é equivalente a posição desta coluna no banco no comando Select.
O nome da coluna no reader, é o mesmo nome da coluna usado no banco de dados ou o apelido da coluna, caso ela tenha algum.

No exemplo utilizaremos:
Um SqlDataReader que chamaremos de MeuReader
Para gerarmos um Reader, precisamos de um SqlCommand que guarda o comando texto e dispara o método executereader. O retorno deste método do SqlCommand, é o conteudo do MeuReader.
Ex:
Dim sqlConn as new SqlConnection(SuaStringdeConexãocomoBanco)
Dim sqlComando as new SqlCommand("Select Coluna1 as Col1, Coluna2 as Col2, Coluna3 as Col3 From Colunas", sqlConn)
Dim MeuReader as SqlDataReader = sqlComando.ExecuteReader()
Pronto, populamos o nosso DataReader com o resultado de uma consulta Sql.
Esta consulta Sql é um teste e vai me retornar 10000 registros.
Note que eu utilizei apelidos nas colunas, desta maneira não vou me referir a Coluna1 pelo nome, e sim por "Col1" etc...

No primeiro teste, vamos utilizar os índices das colunas. Como citei antes, o índice é a posição da coluna no comando Sql.

While MeuReader.Read()
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item(0).ToString
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item(1).ToString
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item(2).ToString
End While
o .ToString após o item do Reader é porquê o resultado seria um objeto, e no meu caso, este objeto é String. Este objeto pode ser de qualquer valor (string, integer,etc...) cabe a você definir.

No segundo teste, vamos utilizar o nome das colunas no banco. Vamos utilizar o mesmo comando, o mesmo procedimento, só mudando na hora de pegar o valor do banco, alteramos o índice numérico para nome da coluna.

While MeuReader.Read()
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item("col1").ToString
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item("col2").ToString
Grid.Rows(IndiceLinha).Cells(IndiceColuna).Value = MeuReader.Item("col3").ToString
End While
Note que utilizei o alias (apelido) da coluna e não o nome dela. Se na criação do comando Select eu não tivesse nenhum apelido pras colunas, eu usaria no lugar do col1, coluna1 por exemplo.

Resultado: A resposta de tudo isso, foi 0,11 segundos mais rápido se utilizarmos o exemplo novamente, o índice das colunas por número.

Opinião pessoal: Mais uma vez, a pergunta é: Até onde vale a performance? Lógico que buscamos excelência sempre, porém 10.000 linhas, não é toda aplicação que necessita de 10.000 linhas sempre que vai sentir-se prejudicada por 0,11 segundos. Veja bem, não estamos falando nem de 1 segundo! é 1 décimo de segundo. Muito pouco! Para os desenvolvedores, é muito mais visível durante o código se utilizarmos o nome da coluna. Dessa forma sabemos sem precisar procurar o comando disparado, o valor que a coluna deve retornar e o tipo do valor. Fácil e prático.

Melhor ainda: Para dar um "boost" na performance, existe no SqlCommand um ExecuteReader() que usa por parâmetro um System.Data.CommandBehavior. Para deixar realmente mais rápido, podemos utilizar o parâmetro CommandBehavior.SequentialAccess desta forma SqlCommand.ExecuteReader(CommandBehavior.SequentialAccess).
Ele torna o acesso as colunas, sequêncial e único. Ou seja, você é obrigado a usar as colunas na ordem que foi criado o select. No exemplo, você só pode usar as colunas na ordem Coluna1, Coluna2 e Coluna3, e somente uma vez por coluna. Caso você precisasse utilizar a Coluna2 antes da Coluna1, ou precisasse utilizar mais de uma vez a mesma coluna, um exception ia ser disparado. Este comando torna mais rápido porém é restrito. De qualquer forma, caso você precise alterar as ordens ou utilizar mais de uma vez, basta jogar em variáveis na ordem, e depois utilizar estas variáveis. Dai fica tudo rápido e certo! Com isso, tivemos um ganho de mais de 0,6 segundos. O que em 10 consultas gera 6 segundos e assim por diante.

Bom, é isso pessoal. O post saiu maior do que eu esperava mas acredito que deu pra ajudar um pouco. Em breve, iremos executar mais testes e postar aqui. Caso tenha vontade de conhecer alguma coisa mais a fundo comente. Vamos verificar, pode ter certeza.

abraços e até a próxima

1 comentários:

Marcelo de Aguiar disse...

Se tu usar o DataReader, use os metodos GetInt, GetDouble, GetValue... Isso deixa o DataReader ainda mais rapido pois ele não precisar "adivinhar" o tipo de dado que esta vindo do Banco.

fui...