Mostrando postagens com marcador análise de paralelismo. Mostrar todas as postagens
Mostrando postagens com marcador análise de paralelismo. Mostrar todas as postagens

domingo, 20 de julho de 2014

Tutorial: Limitar uso de CPU do paralelismo

Introdução
Paralelismo utiliza os núcleos livres de uma CPU para executar rotinas com maior desempenho. Isso pode ser um problema quando o computador que executa o código é compartilhado e existem outros processos e sistemas rodando que não podem parar.

Nota: para mais informações sobre como o paralelismo funciona, visite o outro artigo do blog: Análise: Programação Paralela

Solução
A solução para este problema não é deixar de criar rotinas paralelas, mas sim limitá-las para não usarem 100% de CPU e permitir que o código seja executado com maior desempenho, mas sempre mantendo o processador com uma folga para a execução de outros processos e sistemas.
Para isso, faremos o uso do ParallelOptions.MaxDegreeOfParallelism.

Exemplo
Utilizaremos um simples loop for em paralelo de 1 até 1.000.000 para analisar o comportamento da CPU.
Na chamada do método Parallel.For, podemos passar por parâmetro um objeto do tipo ParallelOptions, onde defimimos o valor da propriedade MaxDegreeOfParallelism para a quantidade de núcleos da CPU que serão usados no loop.

        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            ParallelOptions options = new ParallelOptions();
            options.MaxDegreeOfParallelism = 3;

            Parallel.For(0, 1000000, options, i =>
            {
                Console.Write(i.ToString() + "; ");
            });

            Console.ReadLine();

        }

Definindo o parâmetro MaxDegreeOfParallelism como 3, significa que, serão feitas até 3 atividades em paralelo simultâneamente.
Em outras palavras, em um computador com 4 núcleos, só serão usados 3 núcleos e o uso de CPU ficaria por volta de 75%.
Segue abaixo algumas imagens que representam o uso de CPU, conforme o valor da propriedade MaxDegreeOfParallelism.

MaxDegreeOfParalelism = 1

MaxDegreeOfParalelism = 2

MaxDegreeOfParalelism = 3

MaxDegreeOfParalelism = 10

Notas finais
Se o valor da propriedade MaxDegreeOfParallelism for um número maior que a quantidade de núcleos da CPU, o processo executará normalmente, usando todos os núcleos disponíveis da CPU. Se o valor da propriedade for -1, serão usados todos os núcleos, se o valor for zero ou um número menor que -1, será lançada uma exception.

Conclusão
Utilizar paralelismo para executar rotinas com maior desempenho realmente pode ser muito viável, mas adicionar limite na execução de um código em paralelo também é um assunto que precisa ser discutido para manter o sistema executando sempre com folga, sem ficar com picos de 100% de uso de CPU, o que pode afetar outros sistemas e processos que estejam em execução no mesmo computador.

Análise: Programação Paralela

Objetivo
Criar rotinas que executem métodos em paralelo e analisar as diferenças entre a execução sem paralelismo dos mesmos métodos.
Nota: para saber mais sobre o conceito de paralelismo, visite o outro artigo do blog: Conceito: Programacao Paralela

Exemplos
Para os códigos abaixo, considere que foi criado um novo projeto do tipo Console Application, com o import/using do namespace System.Threading.Tasks.

Loop For
http://msdn.microsoft.com/pt-br/library/dd783539(v=vs.110).aspx


Utilizaremos o loop for para analisar a ordem das iterações e o consumo de CPU em cada situação.

        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            Console.WriteLine("Loop For Comum");
            for (int i = 0; i < 100; i++)
            {
                Console.Write(i.ToString() + "; ");
            }

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("Loop For Paralelo");
            Parallel.For(0, 100, i =>
            {
                Console.Write(i.ToString() + "; ");
            });

            Console.ReadLine();
        }


Os dois loops vão de 0 até 99, onde o primeiro é sequencial e o segundo é em paralelo, que não garante a ordem das iterações do loop.



Abaixo temos o output do código executado, mostrando que as iterações em loop comum são ordenadas e em paralelo podem ser desordenadas.













Abaixo temos a comparação de uso de CPU entre um loop for comum e um loop for em paralelo, podemos ver que o comum usa apenas um núcleo da CPU e o paralelo usa todos que estão disponíveis.
For Comum
For Paralelo












Parallel.Invoke

Utiliza-se o método Parallel.Invoke para executar um conjunto de métodos de forma paralela. Este método é bloqueante e aguarda todos os métodos serem executados para avançar para a próxima linha de código. Neste exemplo, vamos analisar e comparar o tempo de execução de um código.


        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            DateTime dataInicio, dataFim;
            DateTime dataInicioParallel, dataFimParallel;

            dataInicio = DateTime.Now;
            Esperar(1000);
            Esperar(2000);
            Esperar(3000);
            dataFim = DateTime.Now;

            Console.WriteLine();

            dataInicioParallel = DateTime.Now;
            List<Action> listaAcoes = new List<Action>();
            listaAcoes.Add(() => Esperar(1000));
            listaAcoes.Add(() => Esperar(2000));
            listaAcoes.Add(() => Esperar(3000));
            Parallel.Invoke(listaAcoes.ToArray());
            dataFimParallel = DateTime.Now;

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("Duração comum: " + (dataFim - dataInicio).TotalMilliseconds.ToString("F0") + " ms");
            Console.WriteLine("Duração paralelismo: " + (dataFimParallel - dataInicioParallel).TotalMilliseconds.ToString("F0") + " ms");

            Console.ReadLine();
        }

        static void Esperar(int milissegundos)
        {
            Console.WriteLine("Esperando: " + milissegundos.ToString() + " ms");
            System.Threading.Thread.Sleep(milissegundos);
            Console.WriteLine("Ok! - " + milissegundos.ToString() + " ms");
        }

Output da execução do código:











O método Esperar() foi chamado três vezes, passando por parâmetro 1000, 2000 e 3000 em cada chamada, o que significa que ele iria esperar 1 segundo, depois 2 e depois 3.
Chamando o método as três vezes de maneira tradicional, temos que esperar 6 segundos para a execução total. Chamando paralelamente as três execuções do método, podemos ver que o tempo de execução caiu para 3 segundos; isso se dá pelo motivo que as três chamadas foram iniciadas ao mesmo tempo, ou seja, a contagem de tempo de cada execução do método foi iniciada no mesmo momento, não tendo que esperar o primeiro método acabar para só assim executar o próximo.

Conclusão
Não existem apenas essas duas possibilidades de se usar código em paralelo, esses casos foram citados apenas para servir de análise e comparação de um código comum e um código paralelo.
Não há como dizer se é melhor usar paralelismo ou se é melhor usar a maneira tradicional, pois é a necessidade que faz o uso de um código ou de outro se tornar viável para a aplicação.

Para saber como limitar o uso de CPU de uma rotina paralela, dê uma olhada no próximo artigo: Tutorial: Limitar uso de CPU do paralelismo