Introdução ao PLINQ
O que é uma consulta em paralelo?
Consulta integrada à linguagem (LINQ) foi introduzido na.NET Framework versão 3.0, ele apresenta um modelo unificado para consultar qualquer System.Collections.IEnumerable ou System.Collections.Generic.IEnumerable<T> a fonte de dados em uma maneira de tipo seguro. LINQ to Objects é o nome para consultas LINQ que são executados em relação a coleções na memória, como List<T> e arrays. Este artigo presume que você tenha um entender básica do LINQ. Para obter mais informações, consulte LINQ (consulta integrada à linguagem).
PLINQ (Parallel LINQ) é uma implementação paralela do padrão LINQ. Uma consulta de várias maneiras de PLINQ é semelhante a um LINQ to Objects sem paralelo a consulta de. Consultas PLINQ, assim como seqüencial LINQ consultas, operar em qualquer memória em IEnumerable ou IEnumerable<T> dados de origem e adiaram a execução, o que significa que eles não começar a execução até que a consulta é enumerada. A principal diferença é que o PLINQ tenta fazer uso total de todos os processadores no sistema. Ele faz isso particionando a fonte de dados em segmentos e, em seguida, executar a consulta em cada segmento de threads de trabalho separado em paralelo em vários processadores. Em muitos casos, a execução paralela significa que a consulta executa significativamente mais rápido.
Por meio de execução em paralelo, PLINQ pode alcançar melhorias significativas de desempenho sobre o código herdado para determinados tipos de consultas, muitas vezes apenas adicionando a AsParallel a operação de consulta à fonte de dados. No entanto, o paralelismo pode apresentar suas própria complexidades e nem todas as operações de consulta executados mais rapidamente no PLINQ. Na verdade, paralelização realmente lento determinadas consultas. Portanto, você deve entender como os problemas, como pedidos afetam consultas em paralelo. Para obter mais informações, consulte Aumento de velocidade de compreensão no PLINQ.
Observação
Esta documentação usa expressões lambda para definir os delegados no PLINQ.Se você não estiver familiarizado com as expressões lambda em C# ou Visual Basic, consulte Expressões lambda no PLINQ e TPL.
O restante deste artigo fornece uma visão geral das classes principais PLINQ e discute como criar consultas PLINQ. Cada seção contém links para exemplos de código e informações mais detalhados.
A classe ParallelEnumerable
O System.Linq.ParallelEnumerable classe expõe quase todos funcionalidade. do PLINQ Ele e o restante da System.Linq tipos de namespace são compilados no assembly System.Core.dll. Os projetos padrão de C# e Visual Basic na Visual Studio referência ao assembly e importar o namespace.
ParallelEnumerableinclui implementações de todos os operadores de consulta padrão que oferece suporte a LINQ to Objects, embora ele não tenta paralelizar cada um deles. Se você não estiver familiarizado com LINQ, consulte Introdução ao LINQ.
Além de para os operadores de consulta padrão, o ParallelEnumerable classe contém um conjunto de métodos que permitem comportamentos específicos para execução paralela. Esses métodos de PLINQ específicos estão listados na tabela a seguir.
Operador de ParallelEnumerable |
Descrição |
---|---|
O ponto de entrada para PLINQ. Especifica que o resto da consulta deve ser paralelizado, se for possível. |
|
Especifica que o resto da consulta deve ser executado em seqüência, como uma consulta do LINQ não paralelos. |
|
Especifica que o PLINQ deve preservar a ordenação da seqüência de origem para o resto da consulta, ou até que a ordenação é alterada, por exemplo, pelo uso de uma cláusula orderby (Order By no Vlsual Basic). |
|
Especifica que o PLINQ para o resto da consulta não é necessário para preservar a ordem da seqüência de origem. |
|
Especifica que o PLINQ deve monitorar o estado do token fornecido cancelamento e cancelar a execução se solicitado periodicamente. |
|
Especifica o número máximo de processadores PLINQ deve usar para paralelizar a consulta. |
|
Fornece uma dica sobre como o PLINQ deve, se possível, mesclar os resultados paralelos volta apenas uma seqüência no segmento de consumo. |
|
Especifica se o PLINQ deve paralelizar a consulta, mesmo quando o comportamento padrão seria para executá-lo seqüencialmente. |
|
Um método de enumeração multithread que, ao contrário de iterar sobre os resultados da consulta, permite resultados para serem processados em paralelo sem mesclar primeiro volta para o thread de consumidor. |
|
Aggregatesobrecarga |
Uma sobrecarga que é exclusivo para o PLINQ e permite que a agregação de intermediária sobre partições de segmento local, além de uma função de agregação final para combinar os resultados de todas as partições. |
O modelo de Opt-in
Quando você escrever uma consulta, optar PLINQ, chamando o ParallelEnumerable.AsParallel o método de extensão na fonte de dados, conforme mostrado no exemplo a seguir.
Dim source = Enumerable.Range(1, 10000)
' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
Where Compute(num) > 0
Select num
var source = Enumerable.Range(1, 10000);
// Opt-in to PLINQ with AsParallel
var evenNums = from num in source.AsParallel()
where Compute(num) > 0
select num;
O AsParallel o método de extensão vincula os operadores de consulta subseqüentes, nesse caso, where e select, para o System.Linq.ParallelEnumerable implementações.
Modos de execução
Por padrão, o PLINQ é conservadora. Em tempo de execução, a infra-estrutura PLINQ analisa a estrutura geral da consulta. Se a consulta for provável que produzir aumentos de velocidade por paralelização, PLINQ partições a seqüência de origem em tarefas que podem ser executados simultaneamente. Se não estiver seguro de paralelizar uma consulta, PLINQ apenas executa a consulta seqüencialmente. Se PLINQ tem uma escolha entre um algoritmo paralelo caro ou um algoritmo seqüencial de baixo custo, ele escolhe o algoritmo seqüencial por padrão. Você pode usar o WithExecutionMode<TSource> método e a System.Linq.ParallelExecutionMode enumeração para instruir o PLINQ para selecionar o algoritmo paralelo. Isso é útil quando você sabe, testando e medida uma determinada consulta executado mais rápido em paralelo. Para obter mais informações, consulte Como: Especificar o modo de execução no PLINQ.
Grau de paralelismo
Por padrão, o PLINQ utiliza todos os processadores no computador host, até um máximo de 64. Você pode instruir o PLINQ para usar não mais do que um determinado número de processadores usando o WithDegreeOfParallelism<TSource> método. Isso é útil quando você deseja certificar-se de que outros processos em execução no computador recebem uma certa quantidade de tempo de CPU. O trecho a seguir limita a consulta utilizando um máximo de dois processadores.
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
Where Compute(item) > 42
Select item
var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
where Compute(item) > 42
select item;
Em casos onde uma consulta está realizando uma quantidade significativa de trabalho de computação vinculados como, por exemplo, e/S de arquivo, pode ser benéfico especificar um grau de paralelismo maior que o número de núcleos na máquina.
Encomendado Versus desordenadas consultas em paralelo
Em algumas consultas, o operador de consulta deve produzir resultados preservar a ordenação da seqüência de origem. PLINQ fornece a AsOrdered operador para esta finalidade. AsOrderedé diferente de AsSequential<TSource>. Um AsOrdered seqüência ainda é processada em paralelo, mas seus resultados são armazenados em buffer e classificados. Porque a preservação da ordem normalmente envolve o trabalho extra, uma AsOrdered seqüência pode ser processada mais lentamente do que o padrão AsUnordered<TSource> seqüência. Se uma determinada operação paralela ordenada é mais rápido do que uma versão seqüencial da operação depende de muitos fatores.
O exemplo de código a seguir mostra como optar preservação da ordem.
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
Where num Mod 2 = 0
Select num
evenNums = from num in numbers.AsParallel().AsOrdered()
where num % 2 == 0
select num;
Para obter mais informações, consulte Preservação da ordem PLINQ.
Paralelos vs.Consultas seqüenciais
Algumas operações exigem que os dados de origem seja entregue de forma seqüencial. O ParallelEnumerable consulta operadores reverter ao modo de seqüencial automaticamente quando necessário. Operadores de consulta definida pelo usuário e os representantes de usuário que exigem a execução seqüencial, PLINQ fornece a AsSequential<TSource> método. Quando você usa AsSequential<TSource>, todos os operadores subseqüentes na consulta são executados seqüencialmente, até AsParallel é chamado novamente. Para obter mais informações, consulte Como: Combinar consultas do LINQ paralelas e seqüenciais.
Opções para mesclar os resultados da consulta
Quando uma consulta PLINQ é executado em paralelo, seus resultados de cada thread de trabalho devem ser mesclados de volta para o segmento principal para o consumo por um foreach loop (For Each na Visual Basic), ou inserção em uma lista ou array. Em alguns casos, pode ser benéfico para especificar um determinado tipo de operação de mesclagem, por exemplo, para começar a produzir resultados mais rapidamente. Para essa finalidade, o PLINQ oferece suporte a WithMergeOptions<TSource> método e o ParallelMergeOptions enumeração. Para obter mais informações, consulte Mesclar as opções de PLINQ.
O operador ForAll
No seqüencial LINQ consultas de execução é adiada até que a consulta é enumerada em um foreach (For Each na Visual Basic) loop ou por invocando um método como ToList<TSource> , ToTSource> , ou ToDictionary. No PLINQ, você também pode usar foreach para executar a consulta e percorrer os resultados. No entanto, foreach propriamente dito não é executado em paralelo, e portanto, requer que a saída de todas as tarefas paralelas mesclados volta para o segmento em que o loop está em execução. PLINQ, você pode usar foreach Quando você deve preservar a ordenação final dos resultados da consulta, e também sempre que você está processando os resultados de forma serial, por exemplo, quando você está chamando Console.WriteLine para cada elemento. Para rápida execução da consulta quando a preservação da ordem não é necessária e quando o processamento dos resultados pode propriamente dito ser paralelizado, use o ForAll<TSource> método para executar uma consulta PLINQ. ForAll<TSource>Execute esta etapa de mesclagem final. O exemplo de código a seguir mostra como usar o ForAll<TSource> método. System.Collections.Concurrent.ConcurrentBag<T> é usado aqui porque ele é otimizado para vários segmentos, adicionando simultaneamente sem tentar remover itens.
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num
' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))
var nums = Enumerable.Range(10, 10000);
var query = from num in nums.AsParallel()
where num % 10 == 0
select num;
// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll((e) => concurrentBag.Add(Compute(e)));
A ilustração a seguir mostra a diferença entre foreach e ForAll<TSource> com relação à execução de consulta.
Cancelamento
PLINQ é integrado com os tipos de cancelamento no .NET Framework 4. (Para obter mais informações, consulte Cancelamento.) Portanto, ao contrário de LINQ to Objects seqüencial consultas de, consultas PLINQ podem ser canceladas. Para criar uma consulta PLINQ cancelável, use o WithCancellation<TSource> o operador na consulta e fornecer um CancellationToken instância como argumento. Quando o IsCancellationRequested no token for definida como true, o PLINQ perceberá, parar o processamento de todos os threads e lançar um OperationCanceledException.
É possível que uma consulta PLINQ pode continuar processar alguns elementos depois que o token de cancelamento é definido.
Para maior capacidade de resposta, você também pode responder às solicitações de cancelamento de delegados de usuário de execução demorada. Para obter mais informações, consulte Como: Cancelar uma consulta PLINQ.
Exceções
Quando é executado uma consulta PLINQ, várias exceções podem ser geradas simultaneamente de diferentes segmentos. Além disso, o código para manipular a exceção talvez em um thread diferente o código que gerou a exceção. PLINQ utiliza a AggregateException tipo de encapsular todas as exceções que foram lançadas por uma consulta e empacotar essas exceções de volta para o segmento de chamada. No thread de chamada, somente um bloco de try-catch é necessário. No entanto, você pode iterar por meio de todas as exceções que são encapsuladas na AggregateException e capturar qualquer um que você pode recuperar com segurança. Em casos raros, algumas exceções podem ser lançadas não encapsulados em um AggregateException, e ThreadAbortExceptions também não estão quebradas.
Quando são permitidas exceções a emergir volta para o segmento de ingresso, em seguida, é possível que uma consulta pode continuar a processar alguns itens depois que a exceção é gerada.
Para obter mais informações, consulte Como: Tratar exceções em uma consulta PLINQ.
Partitioners personalizadas
Em alguns casos, você pode melhorar o desempenho da consulta, escrevendo um partitioner personalizado que aproveita algumas características dos dados de origem. Na consulta, o partitioner personalizado propriamente dito é o objeto enumerável consultado.
[Visual Basic]
Dim arr(10000) As Integer
Dim partitioner = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))
[C#]
int[] arr= ...;
Partitioner<int> partitioner = newMyArrayPartitioner<int>(arr);
var q = partitioner.AsParallel().Select(x => SomeFunction(x));
O PLINQ oferece suporte a um número fixo de partições (embora os dados podem ser dinamicamente reatribuídos a essas partições durante o tempo de execução para balanceamento de carga.). Fore ForEach suporte somente particionamento dinâmico, o que significa que o número de partições de alterações em tempo de execução. Para obter mais informações, consulte Partitioners personalizados para PLINQ e TPL.
Medir o desempenho do PLINQ
Em muitos casos, uma consulta pode ser paralelizada, mas a sobrecarga de definição de consulta paralela supera o benefício de desempenho obtido. Se uma consulta não executa muito computação ou se a fonte de dados for pequena, uma consulta PLINQ pode ser mais lenta do que uma LINQ to Objects seqüencial de consulta de. Você pode usar o analisador de desempenho paralelo no Visual Studio do Team Server para comparar o desempenho de várias consultas, para localizar gargalos de processamento e para determinar se sua consulta está sendo executado em paralelo ou seqüencialmente. Para obter mais informações, consulte Visualizador de simultaneidade e Como: Medir o desempenho da consulta PLINQ.