Essa semana resolvi fazer um comparativo entre LINQ e PLINQ, porém ao final da execução do projeto percebi que o PLINQ sempre levou cerca de 1 segundo a mais de processamento. Bem ao parar para analisar direito e pesquisar no MSDN 1 percebi onde estava meu erro.
Existem alguns pontos que devem ser observados quanto ao PLINQ:
1. Ele sempre melhora a desempenho? Não!
2. PLINQ sempre processa em paralelo? Não!
O PLINQ realmente tende a ser mais performático que o LINQ, porém deve-se observar que nem sempre será. Mas então, onde eu errei no meu teste?
O meu erro foi executar um teste com uma instrução de LINQ normal que não exigia muitos cálculos nem sequer permitia a sua execução em paralelo. O PLINQ nem sempre irá melhorar a desempenho do código, para tal é necessário estudar sua viabilidade, um exemplo prático de onde isso se aplica é na utilização de Entity FrameWork e LINQ to SQL, porque tentar utilizar processamento paralelo no programa se o real processamento está no banco? O SGBD quem deve ter paralelismo neste caso.
O PLINQ é extremamente útil na utilização em objetos, ai sim temos um poderoso aliado para ganho de desempenho na execução das aplicações em computadores multiprocessados. Ainda assim, para que o PLINQ execute em paralelo, será necessário que obedeça algumas regras básicas. A documentação1 do PLINQ descreve alguns itens que são levados em consideração como Queries com Where indexado, ou Skip, Take, concat, além de outros.
Para demonstrar de maneira rápida a utilização do PLINQ iremos implementar um PLINQ em uma classe LINQ to Sql e em um DataSet. Ao consumir a DataContext (Linq to SQL) veremos que o processamento tanto em LINQ quanto em PLINQ será muito proximo. No segundo, como o DataSet é um objeto na memória veremos certa diferença.
O banco de exemplo foi criado para este artigo e sua tabela terá 3 campos: um integer, um string e um DateTime. Como nosso objetivo é teste de performance, é bom que a base tenha uma quantidade considerável de registros, para tal, nosso script realiza o insert de 1.000.000 registros na base conforme listagem abaixo.
--Criação do banco
Create database TestePLINQ
GO
--Uso do DataBase
use TestePLINQ
GO
--Tabela de Conteúdo
create table tabelaTeste (
id int not null
,dataCriacao datetime null
,texto varchar(200) null
)
GO
--Procedure para popular a tabela
Create procedure FcInclui
AS
BEGIN
declare @incremental as integer
select @incremental = (select IsNull(MAX(id),1) from tabelaTeste)
while @incremental < 1000000
BEGIN
insert into TestePLINQ.dbo.tabelaTeste(id,dataCriacao,texto) values (@incremental,GETDATE(),NEWID())
print @incremental
set @incremental = @incremental + 1
END;
END;
GO
--execução da procedure
exec FcInclui
Nosso primeiro teste será com o LinqToSql2 para comparação entre LINQ e PLINQ, o que nesse caso o PLINQ não deve trazer ganho de desempenho. Utilizaremos 2 cenários neste teste, 1) realizando consulta PLINQ simples com um count, o que nos permite paralelismo; 2) utilizando Take que, segundo a documentação oficial no MSDN por padrão leva o PLINQ a executar em modo linear.
O projeto utilizado será simples, um Windows Form Application com um botão “Iniciar teste” e 3 Labels, 1 com a hora de inicio da execução, 1 com a hora final e o ultimo com o tempo total de execução em segundos.
Ao utilizar o PLINQ com o count obtivemos cerca de 775 milissegundos de execução (Fig 1), enquanto que, com o LINQ tivemos aproximadamente 363 milissegundos (Fig 2). A escolha do count neste caso se dá pelo fato de ele exigir o processamento dos dados diretamente na query, além de permitir que o PLINQ processe em paralelo.
//PLINQ com Count
var dadosLinqToSql = (from t in testePlinq.tabelaTestes.AsParallel()
select t).Count();
//LINQ com Count
var dadosLinqToSql = (from t in testePlinq.tabelaTestes
select t).Count();Figura 1 Tempo de execução com PLINQ
Figura 2 Tempo de execução com LINQ
Já ao utilizar o PLINQ com o Take, temos um tempo de processamento mais próximo entre LINQ e PLINQ, uma vez que o PLINQ tende a processar a requisição de forma linear. Ao se observar as figuras 3 e 4 podemos ver a diferença menor de processamento.
//PLINQ com Take
var dadosLinqToSql = (from t in testePlinq.tabelaTestes.AsParallel()
select t).Take(50000);
//LINQ com Take
var dadosLinqToSql = (from t in testePlinq.tabelaTestes
select t).Take(50000);
Figura 3 PLINQ com Take
Figura 4 LINQ com Take
Agora nosso teste será com um DataSet, por se tratar de um objeto na memória o PLINQ tende a nos oferecer muito mais performance. A idéia é simples, fazer uma classe que execute o select na base e retorne um DataSet para ser consumido por LINQ, para isso foi criada a classe abaixo.
public class TabelaTeste
{
public static DataSet GetTabelaTeste() {
using (SqlConnection sqlConn = new SqlConnection(@"Data Source=MESSASMACHINE\MESSASSQL;Initial Catalog=TestePLINQ;Integrated Security=True"))
{
//declaração DataSet Retorno
DataSet dataSetTeste = new DataSet();
//declaração DataAdapter
SqlDataAdapter sqlAdapter = new SqlDataAdapter();
//Objeto SqlCommand
SqlCommand sqlCommand = new SqlCommand();
//Adiciona o objeto de conexão ao sqlCommand
sqlCommand.Connection = sqlConn;
//Adiciona o a query ao commandText
sqlCommand.CommandText = @"select id,texto,dataCriacao
from tabelaTeste";
//adiciona a query ao Adapter
sqlAdapter.SelectCommand = sqlCommand;
//Preenche o DataSet
sqlAdapter.Fill(dataSetTeste);
return dataSetTeste;
}
}
}
Ao analisarmos o resultado da execução com o DataSet veremos um ganho de desempenho com o PLINQ, além de vermos o balanceamento de carga entre os núcleos, enquanto o LINQ sobrecarrega somente um núcleo, veja imagens 5 e 6.
Figura 5 PLINQ com DataSet
Figura 6 Linq com DataSet
Novamente, ao utilizarmos o PLINQ com o Take, vemos que as diferenças entre sua execução e o LINQ diminuem, por sua opção de processamento linear, conforme figuras 7 e 8.
Figura 7 PLINQ com Take no dataSet
Figura 8 LINQ com Take no DataSet
Como até mesmo a própria Microsoft afirma, é necessário avaliar a situação em que pretende empregar o PLINQ, nem sempre ele será satisfatório, se antes era coisa de DBA estudar performance de query, agora cabe ao desenvolvedor também pensar no assunto, vale a pena acessar o MSDN e revisar sobre o PLINQ, como qualquer recurso feito para agregar qualidade ao software, se mal empregado poderá não servir de nada, ou até piorar a situação.
1 Documentação PLINQ: http://msdn.microsoft.com/en-us/library/dd460688.aspx
2 trabalhando com LINQ to SQL: http://msdn.microsoft.com/en-us/library/bb384396.aspx
Manda ver ae nesse mundo .NET! Você vai longe, garoto!! =D
ResponderExcluir