![]() |
Hoje vamos rever os conceitos básicos da programação paralela na linguagem C# apresentando os principais métodos da PLINQ. |
A PLINQ é a versão paralela da LINQ, e esta disponível a partir da versão 4.0 da plataforma .NET. Ela tem a capacidade de realizar consultas usando a computação paralela, e, dessa forma podemos realizar uma tarefa com PLINQ de forma que ela seja executada concorrentemente.
![]() ![]() |
Para escalar as consultas LINQ para um ambiente com suporte a multiprocessadores a plataforma .NET oferece a PLINQ. E para isso a API da PLINQ é fornecida como uma paridade com a API da LINQ.
Na tabela abaixo vemos a a correspondência entre os namespaces usados na LINQ e os equivalentes da PLINQ :
LINQ |
PLINQ |
System.Collections.IEnumerable |
System.Linq.ParallelQuery |
System.Collections.Generic.IEnumerable<T> |
System.Linq.ParallelQuery<T> |
System.Linq.IOrderedEnumerable<T> |
System.Linq.OrderedParallelQuery<T> |
System.Linq.Enumerable |
System.Linq.ParallelEnumerable |
Observe que a
classe ParallelEnumerable expõe as principais
funcionalidades do PLINQ e inclui implementações de todos os operadores de
consulta padrão que o LINQ suporta.
A seguir veremos alguns dos principais métodos de extensão do classe
ParallelEnumerable usados para execução paralela
destacando os seguintes:
Vamos iniciar como o método AsParallel() e AsOrdered().
Usando AsParallel() e AsOrdered()
O método de extensão AsParallel é o ponto de entrada para o PLINQ e especifica que a consulta deve ser processada em paralelo; ele divide o trabalho em cada processador ou núcleo do processador.
Vejamos um exemplo de como usar este método :
using System; using System.Collections.Generic; using System.Linq;
namespace CPLINQ2
var resultado = from a in alunos.AsParallel()
foreach (var aluno in resultado)
internal class Aluno |
Temos aqui uma
consulta LINQ normal onde temos uma fonte de dados do tipo
Enumerable que retorna uma lista de alunos
com nome e idade :
var resultado = from a in alunos
where a.Nome.StartsWith("C")
select a;
Para tornar essa consulta uma consulta PLINQ incluímos o método AsParallel na fonte de dados e isso faz com que as iterações sejam espalhadas pelos processadores.
var resultado = from a in alunos.AsParallel()
where a.Nome.StartsWith("C")
select a;
Executando veremos que a ordem da sequência de origem não é preservada como mostra a figura :
Isso ocorre pois na PLINQ, o objetivo é maximizar o desempenho, e, assim
uma consulta deve ser executada o mais rápido possível, e produzir os resultados
corretos, mas como a ordenação pode ser uma tarefa que pode ter um grande custo
computacional, por padrão, a PLINQ não preserva a ordem da sequência de origem.
Para preservar a ordem podemos usar o método AsOrdered() :
var resultado = from a in alunos.AsParallel().AsOrdered()
where a.Nome.StartsWith("C")
select a;
O resultado será a sequência sendo exibida conforme a ordem original:
O desempenho da PLINQ
Agora nem sempre tornar uma consulta normal para uma consulta que executa em
paralelo vai te dar um ganho de desempenho.
Na verdade o uso do método AsParallel() não apresenta grandes resultados em todos os tipos de consultas. Para pequenas coleções ele é mais lento que a consulta linq normal.
Vejamos um exemplo onde temos una consulta linq normal que soma os números inteiro de 0 até 327267.
A seguir vamos criar outra consulta igual a essa transformar essa consulta linq em uma consulta Plinq incluindo o método AsParallel na fonte de dados e vamos comparar o resultado:
using System;
using System.Diagnostics;
using System.Linq;
namespace CPLINQ1
{
class Program
{
static void Main(string[] args)
{
int[] numeros = Enumerable.Range(0, short.MaxValue).ToArray();
Console.WriteLine("Pressione algo para iniciar...");
Console.Read();
Stopwatch sw = new Stopwatch();
sw.Start();
var resultado_Normal = numeros.Sum();
Console.WriteLine($"Soma = {resultado_Normal}");
sw.Stop();
Console.WriteLine($"Processamento Normal = {sw.Elapsed}");
sw.Start();
var resultado_Paralelo = numeros.AsParallel().Sum();
Console.WriteLine($"Soma = {resultado_Paralelo}");
sw.Stop();
Console.WriteLine($"Processamento Paralelo = {sw.Elapsed}");
Console.ReadKey();
}
}
}
|
Ao executarmos este código veremos que a consulta Plinq vai demorar mais de 100 vezes que a consulta normal mostrando assim que o custo do paralelismo somente é válido para consultas complexas onde grandes quantidade de dados exigem um processamento mais pesado.
O Grau de Paralelismo
Um conceito
importante relacionado com a programação paralela e com o PLINQ é o
grau de paralelismo.
O grau de paralelismo é um número inteiro sem sinal que indica o número máximo
de processadores que sua consulta PLINQ deve aproveitar em execução. Isso
representa o número máximo de tarefas que seriam executadas simultaneamente para
processar a consulta.
O valor padrão do grau de paralelismo nas consultas PLINQ é 64 o que implica que
a PLINQ pode usar no máximo 64 processadores em seus sistema.
Agora, podemos alterar esse valor usando o método
WithDegreeOfParallelism().
Usando como exemplo a consulta do primeiro exemplo teremos:
var resultado = from a in alunos.AsParallel().AsOrdered().WithDegreeOfParallelism(4)
where a.Nome.StartsWith("C")
select a;
Neste exemplo
estamos limitando o grau de paralelismo na consulta PLINQ a 4 processadores onde
o numero de processadores
foi passado como um argumento para o método
WithDegreeofParallelism.
Em outro artigo veremos os demais métodos da PLINQ.
Pegue o projeto
aqui : CPLINQ1.zip
"Voz do que
clama no deserto: Preparai o caminho do Senhor; endireitai no ermo vereda a
nosso Deus.
Todo o vale será exaltado, e todo o monte e todo o outeiro será abatido; e o que
é torcido se endireitará, e o que é áspero se aplainará."
Isaías 40:3,4
Referências:
C# 8.0 - A instrução switch - Macoratti
C# - Programação Funcional - Exemplos - Macoratti
C# 8.0 - As novidades da nova versão - Macoratti
C# - Coleções Imutáveis - Macoratti
C# - O que há de novo com o C# 9.0 - Macoratti
C# 9.0 - Apresentando Records - Macoratti.net
C# - Sintaxe e conceitos básicos - Macoratti
C# - Os 10 Erros mais comuns dos iniciantes - Macoratti
C# - Otimizando o código - Macoratti