domingo, 20 de julho de 2014

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

Nenhum comentário:

Postar um comentário