Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo fornece informações que ajudarão você a escrever consultas PLINQ que são o mais eficientes possível e que ainda geram resultados corretos.
A principal finalidade do PLINQ é acelerar a execução de consultas LINQ to Objects executando os delegados de consulta em paralelo em computadores multi-núcleo. O PLINQ tem o melhor desempenho quando o processamento de cada elemento em uma coleção de origem é independente, sem nenhum estado compartilhado envolvido entre os delegados individuais. Essas operações são comuns em LINQ to Objects e PLINQ e geralmente são chamadas de "deliciosamente paralelas" porque se prestam facilmente ao agendamento em vários threads. No entanto, nem todas as consultas consistem inteiramente em operações deliciosamente paralelas. Na maioria dos casos, uma consulta envolve alguns operadores que não podem ser paralelizados ou que retardam a execução paralela. Mesmo com consultas que são inteiramente maravilhosamente paralelas, o PLINQ ainda deve particionar a fonte de dados, programar o trabalho nos threads e, normalmente, mesclar os resultados quando a consulta for concluída. Todas essas operações adicionam ao custo computacional de paralelização; esses custos de adição de paralelização são chamados de sobrecarga. Para obter um desempenho ideal em uma consulta PLINQ, o objetivo é maximizar as partes que são deliciosamente paralelas e minimizar as partes que exigem sobrecarga.
Fatores que afetam o desempenho da consulta PLINQ
As seções a seguir listam alguns dos fatores mais importantes que afetam o desempenho da consulta paralela. Essas são instruções gerais que por si só não são suficientes para prever o desempenho da consulta em todos os casos. Como sempre, é importante medir o desempenho real de consultas específicas em computadores com uma variedade de configurações e cargas representativas.
Custo computacional do trabalho geral.
Para obter aceleração, uma consulta PLINQ deve ter um trabalho bastante paralelo agradável para compensar a sobrecarga. O trabalho pode ser expresso como o custo computacional de cada delegado multiplicado pelo número de elementos na coleção de origem. Supondo que uma operação possa ser paralelizada, quanto mais computacionalmente cara for, maior será a oportunidade de aceleração. Por exemplo, se uma função levar um milissegundo para ser executada, uma consulta sequencial com mais de 1000 elementos levará um segundo para executar essa operação, enquanto uma consulta paralela em um computador com quatro núcleos pode levar apenas 250 milissegundos. Isso gera uma aceleração de 750 milissegundos. Se a função exigisse um segundo para ser executada para cada elemento, a aceleração seria de 750 segundos. Se o delegado for muito caro, o PLINQ poderá oferecer uma aceleração significativa com apenas alguns itens na coleção de origem. Por outro lado, pequenas coleções de origem com delegados triviais geralmente não são boas candidatas para PLINQ.
No exemplo a seguir, queryA provavelmente é um bom candidato para PLINQ, supondo que sua função Select envolva muito trabalho. A queryB provavelmente não é uma boa candidata, pois não há trabalho suficiente na instrução Select e a sobrecarga de paralelização irá compensar a maioria ou a totalidade da aceleração.
Dim queryA = From num In numberList.AsParallel() Select ExpensiveFunction(num); 'good for PLINQ Dim queryB = From num In numberList.AsParallel() Where num Mod 2 > 0 Select num; 'not as good for PLINQ
var queryA = from num in numberList.AsParallel() select ExpensiveFunction(num); //good for PLINQ var queryB = from num in numberList.AsParallel() where num % 2 > 0 select num; //not as good for PLINQ
O número de núcleos lógicos no sistema (grau de paralelismo).
Esse ponto é um corolário óbvio para a seção anterior, consultas que são deliciosamente paralelas são executadas mais rapidamente em computadores com mais núcleos porque o trabalho pode ser dividido entre threads mais simultâneos. O aumento geral de velocidade depende da porcentagem do trabalho total da consulta que é paralelizável. No entanto, não suponha que todas as consultas sejam executadas duas vezes mais rápido em um computador de oito núcleos do que em um computador de quatro núcleos. Ao ajustar consultas para um desempenho ideal, é importante medir os resultados reais em computadores com vários números de núcleos. Esse ponto está relacionado ao ponto nº 1: conjuntos de dados maiores são necessários para aproveitar os recursos de computação maiores.
O número e o tipo de operações.
O PLINQ fornece o operador AsOrdered para situações em que é necessário manter a ordem dos elementos na sequência de origem. Há um custo associado à ordenação, mas esse custo geralmente é modesto. As operações GroupBy e Join também incorrem em sobrecarga. O PLINQ tem o melhor desempenho quando tem permissão para processar elementos na coleção de origem em qualquer ordem e passá-los para o próximo operador assim que estiverem prontos. Para saber mais, veja Preservação da ordem em PLINQ.
A forma de execução da consulta.
Se você estiver armazenando os resultados de uma consulta chamando ToArray ou ToList, os resultados de todos os threads paralelos deverão ser mesclados na estrutura de dados única. Isso envolve um custo computacional inevitável. Da mesma forma, se você iterar os resultados usando um loop foreach (For Each in Visual Basic), os resultados dos threads de trabalho precisam ser serializados no thread do enumerador. Porém, se você quiser apenas executar alguma ação com base no resultado de cada thread, poderá usar o método ForAll para executar esse trabalho em vários threads.
O tipo de opções de mesclagem.
O PLINQ pode ser configurado para armazenar sua saída em buffer e produzir em pedaços ou tudo de uma vez após o conjunto de resultados inteiro ser produzido, ou então transmitir resultados individuais à medida que eles são produzidos. O primeiro resulta na diminuição do tempo de execução geral e o último resulta na diminuição da latência entre os elementos gerados. Embora as opções de mesclagem nem sempre tenham um grande impacto no desempenho geral da consulta, elas podem afetar o desempenho percebido porque controlam por quanto tempo um usuário deve esperar para ver os resultados. Para obter mais informações, consulte Opções de Mesclagem no PLINQ.
O tipo de particionamento.
Em alguns casos, uma consulta PLINQ em uma coleção de origem indexável pode resultar em uma carga de trabalho desequilibrada. Quando isso ocorrer, você poderá aumentar o desempenho da consulta criando um particionador personalizado. Para obter mais informações, consulte Particionadores Personalizados para PLINQ e TPL.
Quando PLINQ escolhe o modo sequencial
O PLINQ sempre tentará executar uma consulta pelo menos tão rápido quanto a consulta seria executada sequencialmente. Embora o PLINQ não veja o quão computacionalmente caros são os delegados do usuário ou o tamanho da fonte de entrada, ele procura determinadas "formas" de consulta. Especificamente, ele procura operadores de consulta ou combinações de operadores que normalmente fazem com que uma consulta seja executada mais lentamente no modo paralelo. Quando encontra essas formas, plinq, por padrão, volta ao modo sequencial.
No entanto, depois de medir o desempenho de uma consulta específica, você pode determinar que ela realmente é executada mais rapidamente no modo paralelo. Nesses casos, você pode usar o ParallelExecutionMode.ForceParallelism sinalizador por meio do WithExecutionMode método para instruir o PLINQ a paralelizar a consulta. Para obter mais informações, consulte Como especificar o modo de execução no PLINQ.
A lista a seguir descreve as formas de consulta que o PLINQ, por padrão, executará no modo sequencial:
Consultas que contêm uma opção Select, indexed Where, indexed SelectMany ou ElementAt após um operador de ordenação ou filtragem que removeu ou reorganizou os índices originais.
Consultas que contêm um operador Take, TakeWhile, Skip, SkipWhile e onde os índices na sequência de origem não estão na ordem original.
Consultas que contêm Zip ou SequenceEquals, a menos que uma das fontes de dados tenha um índice originalmente ordenado e a outra fonte de dados seja indexável (ou seja, uma matriz ou IList(T)).
Consultas que contêm o Concat, a menos que seja aplicada a fontes de dados indexáveis.
Consultas que contêm Reverso, exceto quando aplicadas a uma fonte de dados indexável.