Pessoal, antes de começar o post gostaria de pedir desculpas por ter deixado o blog às moscas por mais de um mês. Passei por um período meio complicado da minha vida(tanto pessoal quanto profissional) que me fez ir parar no hospital com crise de stress. Espero que entendam. Desculpas pedidas, vamos ao artigo.
Fabricantes de processadores como Intel e AMD sofriam uma certa pressão do mercado para um grande salto na capacidade de processamento dos computadores. A melhor solução encontrada foi aumentar o número de núcleos de processamento das CPUs, para que se chegasse a clocks mais altos. O surgimento de processadores multi-core no mercado abriu um leque de possibilidades novas aos desenvolvedores, que viram nisso a real possibilidade de aumentar o desempenho de suas aplicações. Baseado nisso, a Microsoft inseriu em sua versão 4.0 do .NET framework, ferramentas para que aplicações feitas em .NET possam distribuir seu processamento sem que os desenvolvedores tenham muito trabalho para implementar isso. E uma dessas ferramentas é o PLINQ(Parallel LINQ), que será um dos tópicos abordados neste artigo.
Quando se comenta sobre o PLINQ, logo as pessoas perguntam: “Por que paralelizar o LINQ?” A resposta é simples: O LINQ é um modelo de programação para quem se preocupa com o que deve ser feito, e não como será feito. Sem o LINQ, essas tarefas seriam executadas utilizando loops e estruturas intermediárias de dados. O problema é que codificando muitas informações específicas, o processador tem dificuldades de paralelizar a tarefa. É bom lembrar que o PLINQ só está disponível para o LINQ to XML e o LINQ to Objects, ou seja: utilizar PLINQ com LINQ to SQL ou com LINQ to Entities não deixará a sua query mais rápida.
Outra pergunta inevitável é: Se vamos fazer uma query com tantos dados utilizando LINQ, por que não utilizar uma base de dados? A resposta é retórica: Quanto código teríamos de escrever se não fôssemos utilizar LINQ? Bem, você trabalharia com a mesma quantidade de dados – e teria o mesmo alto processamento – mas isso teria de ser capturado em uma série de loops FOR não estruturados, chamadas de métodos, e por aí vai. Esta questão implica em dizer que todos os programas devem ter um alto acoplamento com o banco de dados, e eu tenho certeza de que pouquíssima gente concorda com isso.
Para este artigo, utilizaremos a versão Release Candidate(RC) do Visual Studio 2010, já que trabalharemos com o framework 4.0. O download está disponível no próprio site da Microsoft. Após a instalação, crie uma aplicação console em C# e dê a ela o nome que preferir. Em seguida crie uma classe nova de nome Pessoa, como indicado no código abaixo:
class Pessoa
{
public string Nome { get; set; }
public int Idade { get; set; }
public string Telefone { get; set; }
public Pessoa(string nome, int idade, string telefone)
{
this.Nome = nome;
this.Idade = idade;
this.Telefone = telefone;
}
}
O que faremos no form é popular Uma lista genérica (System.Collections.Generic.List) com 3 milhões de registros, com duas formas diferentes: a 1ª será utilizando um laço FOR normal da linguagem, e depois utilizaremos o “novo” for, método FOR da classe Parallel (System.Threading.Tasks.Parallel) , que utiliza paralelismo para executar as funções de laço. Através de um objeto da classe Stopwatch(System.Diagnostics.Stopwatch) vamos monitorar o tempo de execução das tarefas. Vamos começar criando os métodos ListaSemParalelismo() e ListaComParalelismo(). Todos os métodos a seguir estarão na classe Program.cs:
private static List< Pessoa > ListaSemParalelismo()
{
List< Pessoa > lista = new List();
Random rnd = new Random();
for (int i = 0; i < 3000000; i++)
{
lista.Add(new Pessoa("Pessoa " + i, rnd.Next(0, 100),
rnd.Next(999999999).ToString()));
}
return lista;
}
private static List< Pessoa > ListaComParalelismo()
{
List lista = new List();
Random rnd = new Random();
Parallel.For(0, 3000000, delegate(int i)
{
lista.Add(new Pessoa("Pessoa " + i, rnd.Next(0, 100),
rnd.Next(999999999).ToString()));
});
return lista;
}
Também testaremos aqui além do PLINQ, as lamba expressions, aquelas que eu disse em um outro artigo que são mais rápidas que o LINQ, e notaremos resultados surpreendentes. Crie os seguintes métodos para fazermos o teste com lambda:
private static TimeSpan LambdaComParalelismo(List< Pessoa > lista) {
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable listaComParalelismoLambda =
lista.Where(n => n.Idade > 18).
Where(n => n.Idade < 60).
Where(n => n.Telefone.StartsWith("11"))
.AsParallel();
sw.Stop();
return sw.Elapsed;
}
private static TimeSpan LambdaSemParalelismo(List< Pessoa > lista) {
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable listaComParalelismoLambda =
lista.Where(n => n.Idade > 18).
Where(n => n.Idade < 60).
Where(n => n.Telefone.StartsWith("11"));
sw.Stop();
return sw.Elapsed;
}
E por fim, criaremos métodos para executar a mesma query, usando o LINQ to objects e e PLINQ to objects:
private static TimeSpan LINQComParalelismo(List< Pessoa > lista) {
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable
listaComParalelismoLinq =
(from p in lista
where p.Idade > 18 && p.Idade < 60
&& p.Telefone.StartsWith("11")
select p).AsParallel();
sw.Stop();
return sw.Elapsed;
}
private static TimeSpan LINQSemParalelismo(List< Pessoa > lista)
{
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable
listaSemParalelismoLinq =
from p in lista
where p.Idade > 18 && p.Idade < 60
&& p.Telefone.StartsWith("11")
select p;
sw.Stop();
return sw.Elapsed;
}
Métodos criados, vamos aos testes. Insira as seguintes linhas de código no método Main da classe Program.cs:
Stopwatch sw = new Stopwatch();
sw.Start();
List< Pessoa > lista = ListaSemParalelismo();
sw.Stop();
Console.WriteLine("Tempo de geração da lista com for normal: " +
sw.Elapsed);
sw.Reset();
lista.Clear();
sw.Start();
lista = ListaComParalelismo();
sw.Stop();
Console.WriteLine("Tempo de geração da lista com for paralelo: " +
sw.Elapsed);
O resultado que eu obtive aqui foi o seguinte:
Observe que embora a diferença não seja muito grande, o for utilizando paralelismo foi ligeiramente mais rápido. dependendo das operações a serem realizadas(e do hardware da máquina) os resultados podem ser ainda melhores.
Agora, altere o código do seu método Main para que fique da seguinte forma:
List< Pessoa >lista = ListaSemParalelismo();
Console.WriteLine("Tempo do LINQ sem paralelismo" +
LINQSemParalelismo(lista));
Console.WriteLine("Tempo do LINQ com paralelismo" +
LINQComParalelismo(lista));
Console.ReadKey();
Mais uma vez, ponto para o processamento paralelo. Vejam os resultados que obtive aqui:
Observem que a velocidade entre a execução dos comandos já começa a se distanciar, ou seja: O aumento de desempenho fornecido pelo processamento paralelo começou a aparecer um pouco mais.
Mas o resultado mais discrepante ainda está por vir. Vamos alterar nosso método Main para que o código fique da seguinte maneira:
List< Pessoa > lista = ListaSemParalelismo();
Console.WriteLine("Tempo de execução do código com Lambda paralelo: " +
LambdaComParalelismo(lista));
Console.WriteLine("Tempo de execução do código com Lambda normal: " +
LambdaSemParalelismo(lista));
Console.ReadKey();
E vejam só a absurda diferença que eu obtive aqui:
Viram só? A diferença entre uma query Lambda paralela e uma query Lambda normal é absurda! O tempo da query paralela é infinitamente menor do que o tempo da query normal!
Bem pessoal, esta é apenas uma pequena demonstração de uma das novas funcionalidades do .NET Framework 4.0, e do poder da computação paralela, que vem ganhando cada vez mais espaço, pois ainda esse ano teremos no mercado processadores com 8 núcleos.
Todos os exemplos aqui foram executados em um Intel Core2Duo @ 2.0GHz com 2GB de ram. Os resultados podem variar de acordo com o hardware utilizado.
Abraços e keep coding!



Deixar um comentário