Fala galera, finalmente arrumei um tempinho e consegui publicar mais este post! Tá osso!!

Hoje vamos ver mais uma novidade do .Net Framework 4.0, Parallel Programming ou como conhecemos Programação Paralela.

Mas antes de vermos como funciona vamos dar uma olhada no que é a programação paralela!

Programação Paralela é uma forma de programação em que vários cálculos são realizados simultaneamente, operando sob o princípio de que grandes problemas podem ser divididos em problemas menores, que então são resolvidos em paralelo.

A técnica de paralelismo já é empregada por vários anos, principalmente na computação de alto desempenho, mas recentemente o interesse no tema cresceu devido às limitações físicas que previnem o aumento de freqüência de processamento. Com o aumento da preocupação do consumo de energia dos computadores, a computação paralela se tornou o paradigma dominante nas arquiteturas de computadores sob forma de processadores multinúcleo.

Tradicionalmente, o software tem sido escrito para ser executado sequencialmente. Para resolver um problema, um algoritmo é construído e implementado como um fluxo serial de instruções. Tais instruções são então executadas por uma unidade central de processamento de um computador. Somente uma instrução pode ser executada por vez; após sua execução, a próxima então é executada.

Por outro lado, a computação paralela faz uso de múltiplos elementos de processamento simultaneamente para resolver um problema. Isso é possível ao quebrar um problema em partes independentes de forma que cada elemento de processamento pode executar sua parte do algoritmo simultaneamente com outros. Os elementos de processamento podem ser diversos e incluir recursos como um único computador com múltiplos processadores, diversos computadores em rede, hardware especializado ou qualquer combinação dos anteriores.

Para que possamos trabalhar com a programação paralela, a Microsoft introduziu no .Net Framework 4.0 uma nova biblioteca chamada Task Parallel Library ou TPL, que irá dividir as tarefas em múltiplos processadores sem a necessidade de utilizar Threads ou Locks.

Devemos utilizar a classe Parallel do namespace System.Threading.Task, com isso a própria classe se encarrega de escalar a tarefa nos vários núcleos disponíveis.

Para que você possa entender melhor, vamos ver alguns exemplos.

Utilizando o método Parallel.For()

Não abordarei a criação de projetos visto que todos são do tipo Console Application.

Listagem 01 – execução normal

private static void NormalFor()
{
    var sw = new Stopwatch();
    sw.Start();

    for (int i = 0; i < 500; i++)
    {
        Console.WriteLine("Thread ID = {0} - x = {1}", Thread.CurrentThread.ManagedThreadId, i);
        Thread.Sleep(100);
    }

    Console.WriteLine("Tempo de execução: {0} ms", sw.ElapsedMilliseconds);
    sw.Stop();
}
 

Listagem 02 – utilizando programação paralela

private static void ParallelFor()
{
    var sw = new Stopwatch();
    sw.Start();

    Parallel.For(0, 500, WriteX); 
    
    Console.WriteLine( "Tempo de execução: {0} ms", sw.ElapsedMilliseconds );
    Thread.Sleep(10000);
    sw.Stop();
}

private static void WriteX(int x)
{
    Console.WriteLine("Thread ID={0} - x={1}", Thread.CurrentThread.ManagedThreadId, x);
    Thread.Sleep(100);
}

 

Conforme podem observar na Figura 01 o processamento ocorre somente em uma thread, já na Figura 02 são utilizadas várias threads para realizar o processamento o que diminui o tempo de execução do método, temos então, uma melhora significativa na performance.

parallel_01
Figura 01 – execução normal

parallel_02
Figura 02 – execução paralela

 

Vamos ver um exemplo um pouco mais complexo porém que ilustra bem o paralelismo.

Utilizamos além do método For o ForEach.

Calculando PI

O número IP é chamado um número transcendental. Isto significa que ele tem um decimal infinito repetindo que não mostra nenhum padrão. Matemáticos ao redor do mundo já tentaram encontrar um padrão para ele, porém até o momento sem sucesso.

Na figura 03 temos a fórmula matemática para calculo do PI.

PI
Figura 03 – Fórmula para cálculo do PI

Na listagem 01 temos uma implementação de cinco métodos que utilizam algoritmos normais e programação paralela para calculo do PI.

Criem um projeto do tipo Console Application utilizando o framework 4.0 e insira o código da Listagem 03.

Ao executarem o código verão que o tempo de resposta é menor nos métodos que utilizam a programação paralela.

 

Listagem 03

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleParallel
{
    internal class Program
    {
        private const int NumSteps = 100000000;

        private static void Main()
        {
            while (true)
            {
                Time(SerialLinqPi);
                Time(ParallelLinqPi);
                Time(SerialPi);
                Time(ParallelPi);
                Time(ParallelPartitionerPi);

                Console.WriteLine("----");
                Console.ReadLine();
            }
        }

        private static void Time(Func<double> work)
        {
            Stopwatch sw = Stopwatch.StartNew();
            double result = work();
            Console.WriteLine(sw.Elapsed + ": " + result);
        }


        private static double SerialLinqPi()
        {
            const double step = 1.0/NumSteps;
            return (from i in Enumerable.Range(0, NumSteps)
                    let x = (i + 0.5)*step
                    select 4.0/(1.0 + x*x)).Sum()*step;
        }


        private static double ParallelLinqPi()
        {
            const double step = 1.0/NumSteps;
            return (from i in ParallelEnumerable.Range(0, NumSteps)
                    let x = (i + 0.5)*step
                    select 4.0/(1.0 + x*x)).Sum()*step;
        }


        private static double SerialPi()
        {
            double sum = 0.0;
            const double step = 1.0/NumSteps;
            for (int i = 0; i < NumSteps; i++)
            {
                double x = (i + 0.5)*step;
                sum = sum + 4.0/(1.0 + x*x);
            }
            return step*sum;
        }


        private static double ParallelPi()
        {
            double sum = 0.0;
            const double step = 1.0/NumSteps;
            var monitor = new object();
            Parallel.For(0, NumSteps, () => 0.0, (i, state, local) =>
                                                      {
                                                          double x = (i + 0.5)*step;
                                                          return local + 4.0/(1.0 + x*x);
                                                      }, local => { lock (monitor) sum += local; });
            return step*sum;
        }

        private static double ParallelPartitionerPi()
        {
            double sum = 0.0;
            const double step = 1.0/NumSteps;
            var monitor = new object();
            Parallel.ForEach(Partitioner.Create(0, NumSteps), () => 0.0, (range, state, local) =>
                                                                              {
                                                                                  for (int i = range.Item1; i < range.Item2; i++)
                                                                                  {
                                                                                      double x = (i + 0.5)*step;
                                                                                      local += 4.0/(1.0 + x*x);
                                                                                  }
                                                                                  return local;
                                                                              }, local => { lock (monitor) sum += local; });
            return step*sum;
        }
    }
}

 

Como puderam observar, o paralelismo reduz significamente o tempo de processamento. Em alguns casos onde os laços ou cálculos forem muito simples talvez você não perceba a diferença entre a execução do algoritmos em modo “normal” ou em paralelo.

Espero que tenham entendido e gostado!!!